精华 XIUNO BBS X版 更新记录贴 20260617

# 2026-06-17 更新日志

## Bug 修复

### 积分每日限制次数不生效
- **问题**:后台积分规则设置的"每日限制次数"无效,用户操作不受次数限制
- **原因**:`CreditsRuleService::applyRule()` 中每日限制检查被 `!$checkOnly` 条件跳过,导致预检查阶段不检查每日限制,操作已执行后才检查
- **修复**:移除 `!$checkOnly` 条件,预检查阶段也检查每日限制,超额时直接阻止操作
- **修改文件**:`service/CreditsRuleService.php`

### 删除操作无积分变动提醒
- **问题**:删除主题/回复时,积分扣除没有提醒用户
- **原因**:`route/post.php` 中删除操作的 `applyRule` 返回值被忽略,`message()` 未传 `change_desc`;且删除操作没有积分预检查
- **修复**:删除前添加积分预检查(余额不足阻止删除),删除后捕获积分变动结果并显示 toast 提醒
- **修改文件**:`route/post.php`

## 功能优化

### 积分扣除操作前置确认弹窗
- **需求**:任何积分净变化为扣除的操作,执行前必须弹窗确认
- **实现**:
  - 新增 `XN.confirmCreditsDeduct(event, fid, callback)` 通用函数,调用后端预检查 API,有扣减弹 Bootstrap 5 Modal 确认
  - 替换所有原生 `confirm()` 为 `XN.confirm()` Modal 弹窗
  - 覆盖场景:点赞、收藏、发帖、快速回复、删除主题、删除回复
  - 余额不足时 toast 提示并阻止操作
  - 弹窗显示扣减详情和当前余额
- **修改文件**:
  - `route/my.php` — credits_check API 增加余额返回
  - `view/js/xiuno-modern.js` — 新增 XN.confirmCreditsDeduct 函数
  - `view/htm/thread.htm` — 点赞/收藏/快速回复积分确认
  - `view/htm/post.htm` — 发帖积分确认
  - `view/htm/header.inc.htm` — 全局暴露 creditsCheckUrl
  - `view/js/bbs.js` — 删除操作增加积分确认

## 2026-06-17 第二轮修复

### 新增 unlike/unfavorite 积分事件
- **需求**:取消点赞/取消收藏时也需要积分设置和动作提醒
- **实现**:
  - 新增 `unlike`(取消点赞)和 `unfavorite`(取消收藏)两个积分事件
  - 管理员可在后台积分规则页面独立设置取消时的积分变化
  - `thread.php` 中取消点赞/收藏时应用对应规则
  - 前端 htmx:confirm 监听器根据按钮当前状态(已点赞/已收藏)判断是 like/unlike 或 favorite/unfavorite
- **修改文件**:
  - `lib/UpgradeService.php` — 补充 unlike/unfavorite 事件插入逻辑
  - `install/install.sql` — 新安装时包含 unlike/unfavorite 事件
  - `lang/zh-cn/bbs_*****.php` — 新增语言包
  - `*****/view/htm/credits_rule.htm` — 全局规则页面显示新事件
  - `*****/view/htm/credits_rule_forum.htm` — 版块覆盖页面显示新事件
  - `service/CreditsRuleService.php` — $subEvents 增加 unlike/unfavorite
  - `route/thread.php` — 取消点赞/收藏时应用 unlike/unfavorite 规则
  - `view/htm/thread.htm` — 前端根据按钮状态判断事件名

### 修复收藏/点赞评论/回复/高级回复弹窗不触发
- **问题**:收藏帖子、点赞评论、点赞回复、回复评论、回复回复、高级回复的积分确认弹窗不触发
- **原因**:tmp 缓存未清理,旧模板被使用
- **修复**:清理 tmp 缓存,确认模板代码正确(hx-confirm 属性、htmx:confirm 监听器逻辑完整)
- **修改文件**:无代码修改,仅清理缓存

### 每日上限问题调试
- **问题**:设置了 thread_post 每日上限1次,但仍多次扣除/奖励
- **排查**:在 `getRule`、`applyRule`、`checkDailyLimit` 中添加 error_log 调试日志,输出规则获取结果、daily_limit 值、SQL 查询和计数结果
- **修改文件**:
  - `service/CreditsRuleService.php` — getRule/applyRule 添加调试日志
  - `lib/CreditsService.php` — checkDailyLimit 添加调试日志
- **下一步**:用户测试后查看 PHP error log 中的 `[credits-debug]` 日志,定位问题根因

### 每日上限根因修复
- **根因**:`CreditsService::checkDailyLimit()` 中使用 `$this->db->quote($reason)` 拼接 SQL,但 Xiuno 的 `db_pdo_mysql::quote()` 方法返回的是**去掉首尾引号**的转义字符串(`substr($pdo->quote($value), 1, -1)`),导致 SQL 变成 `AND reason = thread_post`(语法错误,`thread_post` 被解释为列名),查询失败返回 false,count 被设置为 0,每日限制永远不生效
- **修复**:在 `quote()` 返回值两侧手动添加单引号:`AND reason = '" . $this->db->quote($reason) . "'`
- **附带修复**:`api/v1/thread.php` 的搜索 SQL 也有相同问题,一并修复
- **修改文件**:
  - `lib/CreditsService.php` — checkDailyLimit 修复 quote 引号问题
  - `api/v1/thread.php` — 搜索 SQL 修复 quote 引号问题

### 每日上限表名前缀修复
- **根因**:`checkDailyLimit()` 使用 `global $tablepre` 获取表前缀,但 `$tablepre` 在 CreditsService 类方法中不可用,导致表名缺少 `bbs_` 前缀,SQL 查询 `FROM \`credits_log\`` 不存在
- **修复**:改用 `$this->db->table('credits_log')` 方法获取带前缀的完整表名
- **修改文件**:`lib/CreditsService.php`

### 每日上限达到时不拦截操作(优化用户体验)
- **需求**:每日上限达到时不拦截操作(如发帖),而是允许操作继续,但不扣除/奖励积分,操作完成后提醒用户"本日操作已达上限,本次不发放/扣除积分"
- **实现**:
  - `CreditsRuleService::applyRule()` 每日上限超限时返回 `ok=true, daily_limit_reached=true`,不执行积分变动
  - 前端 `confirmCreditsDeduct` 检测到 `daily_limit_reached` 时直接放行,不弹窗
  - 后端各调用点检查 `daily_limit_reached`,在返回消息中附加提醒
- **修改文件**:
  - `service/CreditsRuleService.php` — applyRule 返回 daily_limit_reached 标志
  - `view/js/xiuno-modern.js` — confirmCreditsDeduct 处理 daily_limit_reached
  - `route/thread.php` — 发帖/点赞/收藏时检查 daily_limit_reached 并提醒
  - `route/post.php` — 回复/删除时检查 daily_limit_reached 并提醒

## 2026-06-17 第三轮修复

### 用户积分变动后用户组不更新
- **问题**:用户积分变动后,用户组没有随之升级
- **根因 1(gid 被过滤)**:`user_update()` 将 `gid` 列入受保护字段 `USER_UPDATE_PROTECTED_FIELDS` 静默移除,而 `user_update_group()` 恰恰通过 `user_update($uid, ['gid'=>...])` 修改用户组,导致 gid 永远写不进库
- **根因 2(缓存未清)**:`CreditsService::updateUserCredits()` 直接调用 `$this->db->update()` 写库,绕过 `user_update()`,未清理 `$g_static_users[$uid]` 内存缓存和 `cache_get("user-$uid")` 持久化缓存,导致 `user_update_group()` 内 `user_read_cache()` 读到旧 credits,积分跨边界时不触发升级
- **调试过程**:
  - 在 `user_update`、`user_update_group`、`CreditsService::add/sub/updateUserCredits` 加 xn_log 调试日志
  - 跨 200 边界测试 uid=2:credits=199→200 应从 102 升到 103,实际 db gid=102 未升级
  - 日志确认 `user_update` 调用了但 db gid 没变,锁定根因 1
  - 缓存日志确认 `cached credits=旧值`,锁定根因 2
- **修复**:
  - `model/user.func.php` `user_update_group` 改用 `user__update` 原始层绕过受保护字段过滤,并手动清理两层缓存
  - `lib/CreditsService.php` `updateUserCredits` 写库后清理 `$g_static_users[$uid][$type]` 和 `cache_delete("user-$uid")`
- **验证**:uid=2 credits=199→200,从 gid=102 成功升到 gid=103(log: `AFTER user__update db gid=103`)
- **修改文件**:
  - `model/user.func.php` — user_update_group 改用 user__update + 手动清缓存
  - `lib/CreditsService.php` — updateUserCredits 写库后清缓存
- **遗留问题**:`user_update_group` 只支持向上升级(找到第一个匹配区间即 return),不支持积分减少时降级。如需降级支持需后续单独处理
- **存量用户修复**:新增升级项 `user_group_resync`(`lib/UpgradeService.php`),遍历所有 `gid >= 100` 用户,根据当前 credits 重新计算并更新用户组。在后台「系统升级」页面(`/*****/?upgrade.htm`)执行此步骤即可一次性修复所有存量用户
- **修改文件**:`lib/UpgradeService.php`(新增 `upgradeUserGroupResync` 方法 + getSteps/executeStep 注册)

## 2026-06-17 第四轮修复

### 搜索功能修复

#### 用户搜索失效(用户名/昵称搜不到)
- **问题**:搜索用户名或昵称搜不到用户
- **原因**:`db_cond_to_sqladd()` 不支持 `array('OR' => array(...))` 语法,会把 `OR` 当作列名生成错误 SQL
- **修复**:改用原生 SQL 查询,`WHERE username LIKE '%关键词%' OR nickname LIKE '%关键词%'`,并先检查 nickname 字段是否存在
- **修改文件**:`route/search.php`

#### 英文关键词搜索结果不准确
- **问题**:搜索 "Phpstorm" 等论坛不存在的英文词,却搜出一堆帖子
- **原因**:FULLTEXT 索引使用 ngram parser,对英文也做 bi-gram 分词("Phpstorm" 被切成 ph/hp/st/to/om 等片段),任何包含这些片段的内容都会被误匹配
- **修复**:将 `NATURAL LANGUAGE MODE` 改为 `BOOLEAN MODE + 双引号`,ngram 下双引号要求片段按顺序连续出现,实现精确短语匹配
- **修改文件**:`route/search.php`(搜索建议、编辑器引用、主搜索共3处 FULLTEXT 查询)

#### 同一帖子在搜索结果中重复出现
- **问题**:搜索"优化"等关键词时,同一帖子出现多次
- **原因**:FULLTEXT 内容搜索只搜 `isfirst=0`(回复),LIKE 回退只搜 `isfirst=1`(主帖),逻辑不一致
- **修复**:统一搜索范围 + 添加去重保障(按 tid 去重)
- **修改文件**:`route/search.php`

#### 搜索范围优化:只搜标题和主帖内容
- **需求**:搜索帖子只搜索标题和主帖内容(isfirst=1),不搜回复
- **理由**:
  - 结果更精准,避免回复中顺带提到的关键词造成噪音
  - 性能更好,post 表数据量远大于 thread 表
  - 结果更稳定,主帖内容固定,不会因回复数量影响权重
- **实现**:FULLTEXT 和 LIKE 回退的内容查询都加上 `isfirst=1` 条件
- **修改文件**:`route/search.php`
最新回复

请先登录后再回复 登录

uid:1 管理
关注
随遇而安,随缘而行
发帖 40
评论 236
粉丝 9
关注 1
发新帖
目录

扫码手机打开本帖