问题
[Bug] 删除主题时未限制 user.threads 下限,重复/异常删除可导致资料页显示负数帖子数
环境
### 环境
- XiunoX 1.1.1
- PHP 8.2
- MySQL / MariaDB
- 用户资料页帖子统计使用 `bbs_user.threads`
### 现象
用户发布主题后删除,资料页中的“帖子”数量可能显示为负数,例如:-1 帖子
但用户主题列表实际为空:
暂无帖子
数据库检查结果示例:
bbs_user.threads = -1
实际可见主题数 = 0
软删除主题数 = 0
待审核主题数 = 0
正常预期应显示:
0 帖子
根因
user.threads 是冗余计数器:
创建已审核主题时,执行
threads + 1;删除主题时,执行
threads - 1。
当前删除路径直接调用:
user__update($uid, array('threads-'=>1));
底层通用更新逻辑会生成类似 SQL:
UPDATE bbs_user
SET threads = threads - 1
WHERE uid = ?
没有任何下限保护。
因此,只要发生以下任一情况:
重复删除请求;
软删除后再次进入扣减路径;
创建与删除计数未对称执行;
历史数据已经不一致;
批量删除请求包含已软删除主题;
就会继续扣减,最终出现:
0 -> -1
资料页只是正常展示了数据库中的错误计数,并非前端渲染问题。
受影响代码
文件:
model/thread.func.php
确认存在用户主题计数递减的多个路径:
// 单主题硬删除
user__update($uid, array('threads-'=>1));
// 批量硬删除
user__update($u, array('threads-'=>$dec));
// 单主题软删除
user__update($uid, array('threads-'=>1));
// 批量软删除
user__update($u, array('threads-'=>$dec));
另一个关联缺陷:批量软删除未跳过已软删除主题
批量软删除逻辑会先读取主题列表,再统一执行统计扣减。
但旧逻辑只判断审核状态:
$_audit = isset($thread['audit_status'])
? intval($thread['audit_status'])
: 1;
if ($_audit != 1) continue;
没有判断:
is_deleted
因此,如果批量请求中包含已经软删除的主题,可能再次扣减:
user.threadsforum.threads全站
runtime.threads
建议修复
1. 对用户主题数使用原子下限扣减
不要使用无条件的:
threads = threads - ?
建议封装统一方法,例如:
function thread_user_threads_decrement($uid, $count = 1) {
$uid = intval($uid);
$count = max(0, intval($count));
if ($uid <= 0 || $count <= 0) {
return 0;
}
$db = $_SERVER['db'];
$table = $db->tablepre . 'user';
return db_exec(
"UPDATE `$table`
SET `threads` = GREATEST(`threads` - $count, 0)
WHERE `uid` = $uid
AND `threads` > 0"
);
}
然后将所有主题删除路径中的:
user__update($uid, array('threads-'=>1));
替换为:
thread_user_threads_decrement($uid);
批量路径改为:
thread_user_threads_decrement($uid, $count);
这样可以保证:
1 -> 0
0 -> 0
而不会出现:
0 -> -1
并且应使用单条 SQL 原子更新,避免“先查询、后判断、再更新”带来的并发竞态。
2. 批量软删除应跳过已删除主题
在统计扣减前增加:
$_is_deleted = isset($thread['is_deleted'])
? intval($thread['is_deleted'])
: 0;
if ($_is_deleted == 1) {
continue;
}
完整判断建议:
$_audit = isset($thread['audit_status'])
? intval($thread['audit_status'])
: 1;
$_is_deleted = isset($thread['is_deleted'])
? intval($thread['is_deleted'])
: 0;
if ($_audit != 1 || $_is_deleted == 1) {
continue;
}
期望行为
用户删除唯一主题后,资料页显示
0 帖子;重复删除同一主题不会继续减少帖子数;
已软删除主题再次经过批量删除请求时,不会重复扣减;
bbs_user.threads、bbs_forum.threads、全站主题统计均不应出现负数;对历史异常数据,至少保证后续操作不会继续扩大负数。
## 可附的复现描述
1. 创建一个新用户,初始 `user.threads = 0`;
2. 发布一个审核通过的主题;
3. 删除该主题,理论结果应为 `user.threads = 0`;
4. 在异常、重复请求或重复批量软删除场景下,观察 `user.threads` 继续减为 `-1`;
5. 打开用户资料页,可看到 `-1 帖子`,但主题列表显示“暂无帖子”。