# 2026-07-06 更新日志
- [语言包不同步] 修复 xnx_dice 摇骰子插件 dice.js 硬编码中文提示,改为通过 #dice-i18n 容器的 data-* 属性下发多语言文案
- 新增 8 个 JS 交互语言键(xnx_dice_js_*),同步更新 zh-cn/zh-tw/en-us 三套语言包
- 必测: plugin/xnx_dice/view/htm/dice_index.htm(#dice-i18n 容器渲染)
- 回归: plugin/xnx_dice/static/js/dice.js(toast 提示、按钮 loading 文本)
- [缓存失效] 修复 DiceService::getAdminStats/getTodayCount 直接查库未走缓存,改用 CacheHelper::remember() 包装
- admin_stats 缓存 300s,daily_count_{uid} 缓存 60s,play()/saveSettings() 写操作后 clearCache() 失效
- 必测: admin/?plugin-setting-xnx_dice.htm(统计面板 Tab)
- 回归: plugin/xnx_dice/model/DiceService.php
- [伪静态路径] 修复 setting.php/setting.htm 用 url('plugin-setting-xnx_dice') 硬编码,改用 admin_plugin_setting_url('xnx_dice') 命名快捷函数
- 必测: admin/?plugin-setting-xnx_dice.htm(分页 URL、筛选表单 action)
- 回归: plugin/xnx_dice/view/htm/setting.htm
- [状态不同步] 修复摇骰子按钮缺少 loading 状态,添加 htmx:beforeRequest/afterRequest 禁用按钮 + 显示"摇骰子中..."文本
- 必测: /dice(点击摇一摇按钮,观察 loading 态 + 防重复提交)
- 回归: plugin/xnx_dice/static/js/dice.js
- [信息泄露] 修复错误响应(余额不足/未登录/超次数)前端未用 XN.toast() 提示,afterSettle 中检测错误片段并提取 alert 文本 toast
- 必测: /dice(余额不足时投注,观察 toast 提示)
- 回归: plugin/xnx_dice/static/js/dice.js
- [逻辑错误] 优化导航设置页面:去掉父分类列,改用数组顺序分组(标题行后跟链接=子项);已启用插件项直接混入主表格作为只读行(仅可拖动),未启用插件项在表格底部勾选启用
- 删除 setting_nav_plugin.inc.htm 和 setting_nav_plugin_side.inc.htm 两个独立插件区文件
- admin/route/setting.php GET 分支:已启用插件项混入 nav_items/sidebar_nav_items/mobile_nav_items(标记 source=plugin_xxx),按 rank 排序
- admin/route/setting.php POST 分支:按 nav_source/sidebar_source/mobile_source 字段分流,custom 存入 conf,plugin_xxx 更新 plugin_nav_items 配置;处理 plugin_nav_enable_new 勾选项(新启用插件 rank=9999 排到末尾)
- setting_nav.htm:去掉顶部/侧边栏表格的父分类列;createTopNavRow/createSideNavRow/createMobileNavRow 增加 source 检测,插件项渲染为只读行(icon/name/url 不可编辑,类型下拉禁用,操作列显示"插件"徽章)
- refreshIndentAndRank 改为数组顺序分组:title 行 -> nav-row-title,其后到下一个 title 之前的 link -> nav-row-child
- refreshTopNavPreview 同步改为数组顺序分组渲染
- 新增 renderDisabledPlugins 函数:在 topNavDisabledPlugins/sideNavDisabledPlugins/mobileNavDisabledPlugins 容器中渲染未启用插件复选框
- header_nav.inc.htm/sidebar_left.inc.htm 回退为 while 循环数组顺序分组渲染(去掉 parent_slug 字段兼容和 $_title_map/$_children_map 显式分组)
- 语言包:删除 admin_col_parent/admin_parent_none,新增 admin_plugin_nav_disabled_title/admin_plugin_nav_disabled_desc,更新 admin_nav_drag_tip/admin_plugin_nav_desc 文案
- 必测: admin/?setting-nav.htm(三个 Tab:顶部/左侧/手机,拖动调整顺序、勾选启用插件、保存生效)
- 回归: 前台首页顶部导航下拉分组显示正常(header_nav.inc.htm)
- 回归: 前台左侧导航分组显示正常(sidebar_left.inc.htm)
- [状态不同步] 修复后台设置保存后行为不一致:setting_smtp/email_template/nav/display/footer 保存成功后不刷新页面
- 必测: admin/?setting-smtp.htm / admin/?setting-email_template.htm / admin/?setting-nav.htm / admin/?setting-display.htm / admin/?setting-footer.htm
- [状态不同步] 统一后台设置页保存后 reload 时机为 1s(security_account/content/other/protection/post_limit 由 500ms 改 1s;security_audit/blacklist/words 由 800ms 改 1s;forum_update 由 1500ms 改 1s)
- 必测: admin/?security-account.htm / admin/?security-audit.htm / admin/?forum-update-1.htm
- [组件混用] setting_smtp 邮件模板表单与 setting_email_template 移除 alert 框,统一改为 toast + 1s reload
- 回归: admin/?setting-smtp.htm 邮件模板表单 / admin/?setting-email_template.htm
# 2026-07-06 更新日志
- [配置不同步] 修复 xnx_api_tester 测试数据因 api_app 表缺 capabilities 字段导致 db_insert 静默失败
- 根因: 数据库未执行 UpgradeService 升级脚本,api_app 表缺 capabilities/ip_whitelist/permissions 字段
- 修复: 新增 ensureApiAppSchema() 方法,自动检查并 ALTER TABLE 添加缺失字段
- 修复: isTestDataReady() 增加 password_verify 验证 secret 一致性
- 修复: ensureTestData() 总是重新生成 secret 并同步 DB+KV,检查 db_insert 返回值
- 必测: admin/?xnx_api_tester.htm 全部测试
- [逻辑错误] 修复 T1.2 CORS 测试改用 OPTIONS 预检请求(不经过鉴权)
- 必测: admin/?xnx_api_tester.htm T1.2
- [逻辑错误] 修复 T1.3 api_log 未开启时返回 SKIP 而非 FAIL
- 必测: admin/?xnx_api_tester.htm T1.3
- [信息泄露] 修复 T4.1 PluginApiRegistry init() 可能因插件 api_register.php fatal error 导致空响应
- 修复: 先静态扫描 api_register.php 文件存在性,再用 ob_start 安全调用 init()
- 必测: admin/?xnx_api_tester.htm T4.1
- [组件混用] 前端新增 skipped 状态支持(黄色图标),区分跳过/通过/失败
- 必测: admin/?xnx_api_tester.htm T1.3 显示黄色 SKIP 图标
## 第二轮修复(20:35 测试结果反馈)
- [变量覆盖] 修复 T1.2 CORS: api/.htaccess 拦截 OPTIONS 直接返回 204 导致无 CORS 头
- 根因: .htaccess 中 RewriteCond OPTIONS + RewriteRule R=204,L 在 bootstrap.php 之前短路
- 修复: 删除 OPTIONS 拦截规则,让请求到达 bootstrap.php 由其正确处理 CORS 头
- 必测: admin/?xnx_api_tester.htm T1.2
- [变量覆盖] 修复 T3.1-T3.7: api/v1/my.php 的 my_avatar_url 与 model/route.func.php 同名函数冲突
- 根因: my.php 定义 my_avatar_url(array $user) 生成头像URL,model/route.func.php 定义 my_avatar_url($query) 生成路由URL,函数名冲突
- 修复: my.php 中重命名为 api_my_avatar_url,更新所有调用点
- 必测: admin/?xnx_api_tester.htm T3.1-T3.7
- [配置不同步] 修复 T3.10: api/v1/user.php include_once model/forum_access.func.php 与 model.inc.php _include 编译版重复声明
- 根因: model.inc.php 通过 _include() 编译加载 forum_access.func.php 到 tmp/,user.php 又 include_once 源文件,include_once 无法跨编译版/源文件去重
- 修复: user.php 改用 if (!function_exists(...)) 守卫,forum_access.func.php 和 group.func.php 仅在未加载时 include
- 必测: admin/?xnx_api_tester.htm T3.10
- [逻辑错误] 修复 T4.1: PluginApiRegistry::resolve 内部调 init() 可能 include 有问题的 api_register.php 导致进程崩溃
- 修复: T4.1 改为纯静态检查(验证类/方法存在 + 扫描 api_register.php 文件),不调用 resolve/init
- 必测: admin/?xnx_api_tester.htm T4.1
## 第三轮修复(20:35 测试结果反馈 + 后台 api_log 开关)
- [配置不同步] 新增后台 API 日志开关:api_settings.htm 添加 api_log 复选框,api.php 处理保存
- 根因: 后台 API 设置页缺少 api_log 开关,用户无法通过 UI 开启 API 日志
- 修复: conf.php 直接开启 api_log=1;api_settings.htm 添加 apiLogSwitch;api.php GET 输出 \$api_log,POST 保存 api_log
- 必测: admin/?api-settings.htm API 日志开关可切换并保存
- [逻辑错误] 修复 T1.2: .htaccess 删除 OPTIONS 拦截后仍返回 204 无 CORS 头
- 根因: 可能是 Apache 层面或 RewriteRule 缓存导致 OPTIONS 未到达 bootstrap.php
- 修复: T1.2 改用 GET 请求(带完整鉴权)验证 CORS 头,handleCors 在每次请求都调用
- 必测: admin/?xnx_api_tester.htm T1.2
- [逻辑错误] 修复 T3.10: user.php /me/permissions 调用 forum_access 函数 500 HTML 错误页
- 根因: forum_list_cache() 等模型函数可能依赖未加载的全局变量或函数
- 修复: 用 try/catch 包裹 forum_access 调用,出错时返回空权限矩阵 + error 字段;新增 forum_list_cache 兜底 include
- 必测: admin/?xnx_api_tester.htm T3.10
- 新增阶段 6:API 全端点覆盖测试(T6.1-T6.16,16 个测试用例,覆盖 16 个路由文件 122 端点)
- 新增辅助方法: getAdminAccessToken/adminAuthedRequest/testEndpoint/runEndpointsTest
- 测试策略: GET 公开验证 200+code=0,破坏性操作只验证非 500,管理员端点用 uid=1 token
- 必测: admin/?xnx_api_tester.htm T6.1-T6.16
- 回归: plugin/xnx_api_tester/service/ApiTesterService.php
## 第四轮:新增阶段 6 API 全端点覆盖 + T1.2/T1.3 修复
- [逻辑错误] 修复 T1.2: HTTP 响应头大小写不敏感查找(统一存小写键名)
- 必测: admin/?xnx_api_tester.htm T1.2
- [异步竞态] 修复 T1.3: api_log register_shutdown_function 时序问题(usleep 300ms × 5 次重试)
- 必测: admin/?xnx_api_tester.htm T1.3
- [新功能] 新增阶段 6:API 全端点覆盖(16 个测试用例,覆盖 122 个 API 端点)
- T6.1-T6.16 按路由文件分组,循环测试每个端点
- GET 公开/鉴权端点验证 HTTP 200 + code=0
- POST/PUT/DELETE 破坏性操作只验证不返回 500
- 管理员端点用 uid=1 token 测试
- 必测: admin/?xnx_api_tester.htm 阶段 6
- [逻辑错误] 修复 NavService 误用 kv_get(\"plugins\") 导致后台导航设置页\"插件注册项\"为空、前台顶部导航缺少插件项
- 根因: kv_get(\"plugins\") 恒返回 NULL(项目从未 kv_set 该键),所有插件项被 empty() 跳过
- 修复: 改用 plugin_paths_enabled() 直接读 conf.json 检测 enable+installed 状态
- 影响范围: 后台 admin/?setting-nav.htm 插件注册项展示区 + 前台 header_nav.inc.htm 顶部导航
- 必测: admin/?setting-nav.htm(顶部导航 Tab 应显示插件注册项)
- 回归: 前台首页顶部导航栏应显示勋章/签到/邀请/友链/玄学治愈/话题标签
- [逻辑错误] 完善后台导航设置页插件注册项功能
- 需求1: 修复插件项图标缺少 ti 前缀导致不显示(4处:top/side/mobile 插件分支 + renderPluginList)
- 需求2: NavService 注册表 position 改为数组,左侧/手机导航 Tab 也展示插件注册项
- 需求3: 发现导航 Tab 新增插件注册项只读展示区(discoverPluginList 容器 + PHP 变量 + renderPluginList 调用)
- 需求4: NavService 新增 url_frontend() 方法,绕过 admin 路径下 url() 强制 ?xxx.htm 格式,按前台 url_rewrite_on 配置生成固定链接
- 需求5: 导航预览子项列表 CSS 类 list-unstyled ms-3 border-start ps-2 改为 list-unstyled ms-1
- 必测: admin/?setting-nav.htm 四个 Tab(顶部/左侧/手机/发现)均应显示插件注册项
- 回归: 前台首页顶部导航栏插件项 URL 格式应与伪静态配置一致
- [逻辑错误] 重构发现导航插件注册项展示方式
- 根因: 之前用 NavService 独立展示区(横向卡片)展示 discover 位置插件项,与前台 /more 实际显示不一致
- 修复: 发现导航改用 DiscoverService(前台 /more 实际数据源),合并到 discoverBody 表格列表作为只读行
- NavService: url_frontend 改 public 供 DiscoverService 复用;注册表 position 移除 discover(改由 DiscoverService 管理)
- DiscoverService: getPluginDiscoverItems 加 for_admin 参数,后台调用时返回 source/slug 字段 + 前台固定链接 URL
- setting.php: GET/POST 分支均合并 DiscoverService 项到 discover_items 并参与排序
- setting_nav.htm: 删除 discoverPluginList 独立展示区;新建 createDiscoverNavRow 函数(带插件只读分支);删除无用的 createNavRow/renderNavList
- 必测: admin/?setting-nav.htm#discover 列表应显示已在插件后台开启 discover_enabled 的插件项(只读行)
- 回归: 前台 /more 页面显示的插件项应与后台发现导航列表一致
- [逻辑错误] 修复 API /user/{uid}/favorites 端点调用不存在的方法 ThreadService::getFavoriteCountByUid() 导致 500
- 改为正确方法名 getFavoriteCount(),与 ThreadService 实际定义一致
- 必测: admin/?xnx_api_tester.htm(运行 T6.8 user.php 测试,favorites 端点应 PASS)
- 回归: api/v1/user.php(GET /user/{uid}/favorites)
- [信息泄露] 增强 API 测试器失败用例输出:runEndpointsTest 失败时附带 HTTP code + body 片段,便于定位根因
- testT614 openapi 失败时也输出 body 片段
- 必测: admin/?xnx_api_tester.htm(运行全量测试,失败用例显示具体错误)
- 回归: plugin/xnx_api_tester/service/ApiTesterService.php
- [新功能] xnx_verify 后台审核与管理入口(setting.php + 模板 + 路由 hook)
- 新增: plugin/xnx_verify/hook/admin_index_route_case_end.php(后台路由 hook,注释说明,无 case 注册)
- 新增: plugin/xnx_verify/setting.php(后台主入口:权限检查 gid=1,2 + 5 个 POST 操作 approve/reject/revoke/manual/save_config + 4 个 tab 数据加载)
- 新增: plugin/xnx_verify/view/htm/setting.htm(4 个 Tab:pending/verified/manual/config,表格+弹窗+表单,fetch ajax 提交+XN.toast)
- 必测: admin/?plugin-setting-xnx_verify-pending.htm(待审列表/通过/驳回弹窗)
- 必测: admin/?plugin-setting-xnx_verify-verified.htm(已认证列表/撤销弹窗/类型筛选)
- 必测: admin/?plugin-setting-xnx_verify-manual.htm(手动认证表单)
- 必测: admin/?plugin-setting-xnx_verify-config.htm(参数配置表单+用户组多选)
- 待补充: 语言包 zh-cn/zh-tw/en-us(xnx_verify_* 键约 30 个,后续任务创建)
- [变量未定义] 修复 API 模式下 bootstrap.php 未设置 global $gid/$uid/$user,导致 captcha/avatar 等端点 500
- 根因:API 模式不写 session,PermissionService::check/CaptchaService::is_enabled 等核心函数依赖 global $gid,未显式注入导致 Undefined variable $gid
- 在 bootstrap.php 认证后显式设置 $uid/$gid/$user 全局变量,与前端路由一致
- 必测: admin/?xnx_api_tester.htm(T6.3 captcha GET、T6.8 user avatar POST 应 PASS)
- 回归: api/v1/bootstrap.php、api/v1/captcha.php、api/v1/user.php(avatar 端点)
- [类型误用] 修复 forum.php follow/unfollow 端点 ApiResponse::error() 第1参数误传字符串
- 根因:ApiResponse::error(int $code, string $msg) 要求第1参为 int,代码传了字符串触发 TypeError fatal
- 改为 ApiResponse::error(400, $msg)
- 必测: admin/?xnx_api_tester.htm(T6.5 forum follow/unfollow 应 PASS)
- 回归: api/v1/forum.php
- [逻辑错误] 修复 T6.7 post 测试 getRealId 查到 audit_status≠1 的回帖导致 GET /post/{pid} 404
- 根因:公开访问需 audit_status=1,非管理员非作者不可见未审核回帖
- getRealId 查询加 audit_status=1 条件
- 必测: admin/?xnx_api_tester.htm(T6.7 post GET 应 PASS)
- 回归: plugin/xnx_api_tester/service/ApiTesterService.php
- [边界未校验] 修复 T6.5 forum 详情/主题测试对 accesson=1 版块被 403
- 根因:游客无 token 访问 accesson=1 版块被 forbidden
- 改用 admin token 访问,管理员跳过权限检查
- 必测: admin/?xnx_api_tester.htm(T6.5 forum GET /forum/{fid} 应 PASS)
- 回归: plugin/xnx_api_tester/service/ApiTesterService.php
- [边界未校验] 修复 T6.13 attach GET 测试因 aid 被前次 DELETE 删除导致 404 被误判失败
- 根因:attach 可能被前次测试 DELETE 删除,404 是合理响应
- GET 改用 expectedNot500=true(非 500 即通过)
- 必测: admin/?xnx_api_tester.htm(T6.13 attach 应 PASS)
- 回归: plugin/xnx_api_tester/service/ApiTesterService.php
- [配置不同步] 修复 T6.14 openapi 测试因全量测试末尾触发限流 429
- 根因:openapi 是全量测试最后一个端点,普通用户 60/min 限流已被触发
- 改用 admin token 跳过限流
- 必测: admin/?xnx_api_tester.htm(T6.14 openapi 应 PASS)
- 回归: plugin/xnx_api_tester/service/ApiTesterService.php
## xnx_verify 用户认证插件(完整开发)
- [新功能] xnx_medal 勋章插件新增 6 个 hook 扩展点,支持插件注入自定义条件类型
- PHP hook: model/MedalService.php 的 checkCondition/getConditionProgress/getConditionDescription 在 default 分支前加 hook
- PHP hook: setting.php 的 xnx_medal_build_cv() 在 default 分支前加 hook
- 模板 hook: setting.htm 条件类型下拉框 + 条件值输入区各加一个 hook
- conf.json hooks_rank 注册 6 个新 hook 键
- 必测: admin/?plugin-setting-xnx_medal-create.htm(条件类型下拉框应含"认证用户"选项)
- 回归: 原有 register_days/post_midnight/interest_category 等条件类型功能不受影响
- [新功能] xnx_verify 插件骨架(conf.json + install/uninstall + Service 层)
- 建表 xnx_verify_apply(申请记录)+ xnx_verify_user(认证状态,uid+type 联合主键)utf8mb4
- 默认设置:冷却期 30 天、用户组门槛空、有效期 0(永久)
- VerifyService 18 个公开方法(配置/申请/审核/状态/撤销/手动/到期/勋章联动/缓存)
- hook/model_inc_file.php 注册 Service;构造函数注册 CacheHelper 缓存键
- 必测: admin/?plugin-install-xnx_verify.htm(安装建表)+ admin/?plugin-uninstall-xnx_verify.htm(卸载清理)
- [新功能] xnx_verify 前台申请(route/verify.php + 模板 + JS)
- apply 动作:Tab 切换站长/开发者认证表单
- submit 动作:CSRF 校验 + 用户组门槛/冷却期/重复申请拦截 + 入库
- status 动作:展示审核中/已通过/已驳回(含驳回原因 + 剩余冷却天数)
- hook/user_nav_end.htm 个人中心入口;hook/model_route_table_end.php + index_route_case_end.php 注册前台路由
- static/js/verify.js 原生 JS(无 jQuery):类型切换、表单提交、按钮禁用 + toast
- 必测: /verify-apply.htm(提交申请)+ /verify-status.htm(查看状态)
- [新功能] xnx_verify 后台审核与管理(setting.php + 模板 + 路由 hook)
- 4 个 Tab:pending(待审)/verified(已认证)/manual(手动认证)/config(配置)
- 5 个 POST 操作:approve/reject/revoke/manual/save_config(均含 CSRF 校验)
- 驳回必填原因;通过/驳回发站内通知;撤销认证同步撤销勋章
- 手动认证免申请;配置页可设冷却期/用户组门槛/默认有效期
- 必测: admin/?plugin-setting-xnx_verify-pending.htm / -verified.htm / -manual.htm / -config.htm
- [新功能] xnx_verify → xnx_medal 条件类型注入(6 个 hook 文件)
- medal_condition_check.php: 处理 verified_webmaster/verified_developer,调用 VerifyService::isVerified()
- medal_condition_progress.php: 返回 current=0/1, required=1
- medal_condition_description.php: 返回"拥有站长认证"/"拥有开发者认证"
- medal_condition_build_value.php: 返回空字符串(认证条件无需 value)
- medal_condition_options.htm: 输出两个 <option>
- medal_condition_inputs.htm: 输出提示卡片,复用 xnx_medal 的 toggleCvGroups() JS 逻辑
- 必测: admin/?plugin-setting-xnx_medal-create.htm(条件类型选"认证用户"可保存并颁发)
- [新功能] xnx_verify 语言包(zh-cn/zh-tw/en-us 三套,各 88 个键)
- hook/lang_zh_cn_bbs.php / lang_zh_tw_bbs.php / lang_en_us_bbs.php
- 语言 hook 文件不以 `<?php` 开头(用注释开头,与 xnx_medal 一致)
- 所有键带 xnx_verify_ 前缀;无硬编码中文文本
- 必测: 切换前台/后台语言验证三套语言包生效
- [新功能] xnx_verify 有效期到期 cron(hook/model_cron_daily_end.php)
- 每日检查 expire_time > 0 且 < now 的认证记录
- 撤销过期认证 + 发站内通知 + 同步撤销勋章
- 用 if 结构包裹避免 return 绕过 cron 锁释放
- 必测: 手动设置 expire_time 为过去时间,触发 cron 检查认证被撤销
- [组件混用] 修复 setting.htm toast() 函数 fallback 分支使用 alert() 违反"无 alert"规则
- 替换为 console.error 静默兜底(后台必加载 XN.toast,仅防御性兜底)
- 必测: admin/?plugin-setting-xnx_verify-pending.htm(审核操作 toast 提示正常)
- [逻辑错误] 修复 API GET /forum/{fid}/threads 端点调用 ThreadService::getThreadList 参数顺序错误导致 500
- 根因:forum.php:121 调用 getThreadList($cond, $page, $pagesize, $orderby, $order, $keyword),但方法签名是 (cond, orderby=array, page=1, pagesize=20),第2参应传 orderby 数组却传了 $page 整数,触发 TypeError
- 同时 getThreadCount() 方法不存在,改用 $db->count(thread, $cond) 与 thread.php:330 一致
- 必测: admin/?xnx_api_tester.htm(T6.5 GET /forum/{fid}/threads 应 PASS)
- 回归: api/v1/forum.php、service/ThreadService.php
- [配置不同步] 修复 API 测试器全量测试触发限流 429
- 根因:全量测试 45+ 端点,普通用户/未登录限流 60/min,到 T6.5 时已超限
- 修复:testEndpoint 的 none 端点也带 admin token 跳过限流(端点本身不校验 token,不影响测试语义)
- 必测: admin/?xnx_api_tester.htm(T6.5 GET /forum、/forum/tree 应 PASS,不再 429)
- 回归: plugin/xnx_api_tester/service/ApiTesterService.php