已排期 【轻度bug】删除主题时未限制 user.threads 下限,重复/异常删除可导致资料页显示负数帖子数

问题

[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.threads

  • forum.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;
}

期望行为

  1. 用户删除唯一主题后,资料页显示 0 帖子

  2. 重复删除同一主题不会继续减少帖子数;

  3. 已软删除主题再次经过批量删除请求时,不会重复扣减;

  4. bbs_user.threadsbbs_forum.threads、全站主题统计均不应出现负数;

  5. 对历史异常数据,至少保证后续操作不会继续扩大负数。


## 可附的复现描述
1. 创建一个新用户,初始 `user.threads = 0`;
2. 发布一个审核通过的主题;
3. 删除该主题,理论结果应为 `user.threads = 0`;
4. 在异常、重复请求或重复批量软删除场景下,观察 `user.threads` 继续减为 `-1`;
5. 打开用户资料页,可看到 `-1 帖子`,但主题列表显示“暂无帖子”。
最新回复

请先登录后再回复 登录

发帖 3
评论 4
粉丝 1
关注 0
发新帖
目录
【轻度bug】删除主题时未限制 user.threads 下限,重复/异常删除可导致资料页显示负数帖子数
此广告位招租
此广告位招租
此广告位招租
广告招租
广告招租
广告招租
广告招租
广告招租
广告招租