如题,用AI检测了下系统源码,给出的反馈如下,请站长参考:
# 修复方案:用户组「发帖免审 / 回帖免审 / 资料免审」取消勾选后刷新重新勾选
> 排查日期:2026-08-17 | 关联版本:XIUNOX v1.1.7
> 状态:**仅排查与方案,未改动任何代码**(按需求暂不实施)
---
## 一、问题现象
后台「用户组 → 编辑用户组」页面,取消勾选 **发帖免审 / 回帖免审 / 资料免审** 三个审核权限后,点击确定:
- 页面提示「编辑成功」并刷新;
- 刷新后三个复选框 **又被勾选**;
- 清理应用缓存 / 浏览器缓存均无效。
其它权限项(允许阅读、允许发帖、版主权限等)保存正常,唯独这三个审核权限不生效。
---
## 二、根因(已定位)
**`lib/PermissionService.php` 的 `tableExists()` 方法使用了错误的配置键读取表前缀,导致 `group_permission` 表被判定为"不存在",写入与读取都被静默跳过。**
### 2.1 错误代码
`lib/PermissionService.php:224-233`:
```php
private static function tableExists(): bool {
static $exists = NULL;
if($exists === NULL) {
global $conf;
$tablepre = $conf['db']['master']['tablepre'] ?? 'bbs_'; // ← 键路径错误(第 228 行)
$row = db_sql_find_one("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '{$tablepre}group_permission'");
$exists = !empty($row);
}
return $exists;
}
```
### 2.2 实际配置结构
`conf/conf.php`(本站真实配置)的表前缀位于 **两层驱动嵌套** 之下:
```php
'db' => array (
'type' => 'pdo_mysql',
'mysql' => array (
'master' => array ( 'tablepre' => 'xiunox_', ... ), // 正确路径 ①
),
'pdo_mysql' => array (
'master' => array ( 'tablepre' => 'xiunox_', ... ), // 正确路径 ②
),
),
```
- `$conf['db']['master']` **不存在**(`db` 下没有直接名为 `master` 的键),取值为 `NULL`;
- `?? 'bbs_'` 兜底生效,`$tablepre` 恒等于 **`'bbs_'`**;
- 实际表前缀是 **`xiunox_`**,于是查询的是不存在的 `bbs_group_permission`;
- `INFORMATION_SCHEMA` 查不到该表 → `tableExists()` 恒返回 `FALSE`。
> 对比:框架标准助手 `xiunophp/db.func.php` 的 `db_check_table_exists()`(519-526 行)与 `db_check_column_exists()`(510-517 行)都用 `$db->tablepre`,**是正确的**。本方法绕开了标准助手,自己拼了错误的 `$conf` 键,是唯一一处问题点。
---
## 三、连锁影响(为什么"保存成功"却什么都没存)
`tableExists()` 是 `PermissionService` 内部所有 `group_permission` 读写的前置开关,返回 `FALSE` 导致三级连锁:
| 方法 | 行为 | 后果 |
|---|---|---|
| `updatePermissions()`(152-179 行) | `if(!self::tableExists()) return FALSE;` **直接返回,不写库** | 三个免审值(`allow_direct_post/reply/profile`)**从未写入** `xiunox_group_permission` |
| `getPermissions()`(120-144 行) | 跳过 `group_permission` 读取,回退 `$grouplist[$gid]`(`bbs_group` 表旧字段) | 设置页勾选状态取自 `bbs_group.allow_direct_*`(默认值 `1`)→ **刷新后又勾选** |
| `check()` / `getPermissionValue()`(82-113 / 241-248 行) | `getPermissionValue()` 返回 `NULL`,回退 `bbs_group` 旧字段 | **运行时**同样把这三个用户组视为「免审=1」,审核永不生效 |
### 3.1 为什么其它权限正常
`admin/route/group.php` 的 POST 处理分两段保存:
1. **第一段**(132-142 行):`allowread / allowthread / allowpost / allowattach / allowdown / 版主权限` 等写进 **`bbs_group` 表**(`group_update()`)→ 走正常路径,**能持久化**;
2. **第二段**(167-174 行):所有注册权限键写进 **`group_permission` 表**(`updatePermissions()`)→ 被 `tableExists()` 拦截,**静默失效**。
三个免审字段**只在**第二段存在,没有 `bbs_group` 表字段的写入路径(`bbs_group` 里的 `allow_direct_*` 列只用于回退读取),所以唯独它们不生效。
### 3.2 运行时同样受影响(不只是显示问题)
- 发帖免审:`lib/security/AuditService.php:46` → `PermissionService::check('allow_direct_post')`
- 回帖免审:`lib/security/AuditService.php:77` → `PermissionService::check('allow_direct_reply')`
- 资料免审:`api/v1/user.php:447` → `PermissionService::check('allow_direct_profile')`
三条运行时判断全部被 `tableExists()` 卡住并回退到 `bbs_group` 旧字段(=1),**即使本次设置页问题修好之前后台已经把某组改成需审核,运行时也仍然放行免审**。
---
## 四、修复方案
> ⚠️ 本方案仅描述改动点,**尚未实施**。按铁律,改动仅限 `lib/PermissionService.php`,不涉及系统源码其它文件。
### 4.1 推荐方案(最小改动,复用框架正确助手)
将 `tableExists()` 改为直接委托给框架标准助手 `db_check_table_exists()`(它已用 `$db->tablepre`,与 `updatePermissions()` 第 158-159 行现有写法一致):
```php
private static function tableExists(): bool {
static $exists = NULL;
if($exists === NULL) {
$exists = db_check_table_exists('group_permission');
}
return $exists;
}
```
- 改动量:`lib/PermissionService.php` 单个方法,约 6 行。
- 安全性:`db_check_table_exists()` 在 `xiunophp/db.func.php:519-526`,参数化绑定 `$db->tablepre.$table`,前缀永远取运行时真实值(`xiunox_`)。
- 一致性:`lib/LoginSecurityService.php:33/84/124`、`lib/security/AuditService.php:1402` 均已用该助手,风格统一。
### 4.2 备选方案(不依赖助手,语义等价)
```php
private static function tableExists(): bool {
static $exists = NULL;
if($exists === NULL) {
global $db;
$tablepre = (is_object($db) && !empty($db->tablepre)) ? $db->tablepre : 'bbs_';
$row = db_sql_find_one("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '{$tablepre}group_permission'");
$exists = !empty($row);
}
return $exists;
}
```
> 与 `HealthCheckService::checkDatabase()`(`lib/HealthCheckService.php:278-288`)同思路:优先 `$db->tablepre`。若担心个别环境 `$db` 未初始化,可再补 `pdo_mysql` / `mysql` 两层 config 回退,但正常请求上下文 `$db` 必然存在,简单写法即可。
### 4.3 不建议的改动(明确排除)
- **不要**在 `admin/route/group.php` 里额外把 `allow_direct_*` 也写进 `bbs_group` 表字段。
- 现状设计是「`group_permission` 表优先、`bbs_group` 旧字段回退」的双源读取(`getPermissions()` 注释明确说明),把免审改成写 `bbs_group` 会造成双源不一致、升级/迁移复杂化;
- 修好 `tableExists()` 后,免审字段走 `group_permission` 单源即可,与其它权限一致。
- **不要**全局删除 `bbs_group` 的 `allow_direct_*` 列:升级脚本(`lib/UpgradeService.php:1270-1271/1400`)仍依赖它们作回退与迁移基础,删除属破坏性变更,超出本次修复范围。
---
## 五、验证步骤(修复后执行)
1. **前置确认**(修复前):SQL 确认当前 `group_permission` 无免审记录——
```sql
SELECT gid, permission_key, value FROM xiunox_group_permission WHERE permission_key LIKE 'allow_direct%';
-- 预期:0 行(此前从未写入成功)
```
2. **单元级**:临时在页面 POST 后检查该表——
```sql
SELECT gid, permission_key, value FROM xiunox_group_permission WHERE gid = 102 ORDER BY permission_key;
-- 修复后预期:17 个注册键全量出现,免审键为本次勾选值(取消勾选 → 0)
```
3. **UI 级**:编辑用户组,取消勾选三个免审 → 保存 → 刷新,确认三个复选框保持未勾选;再勾选保存 → 保持勾选。
4. **运行时级**:将该组免审设为 `0` 后,用该组账号发帖 → 帖子进入「待审核」;设为 `1` → 直接发布。回帖、资料修改(昵称/签名/头像)同理。
---
## 六、风险与注意事项
1. **行为变化提示**:当前运行时由于 bug,所有用户组实际都是「免审=1」。修复后,`PermissionService::check()` 将真正读取 `group_permission` 表值——**若某组此前曾被设置为需审核但从未生效,修复后再次保存才会真正生效**。部署后建议逐一核对关键用户组的免审设置。
2. **数据迁移**:修复前 `group_permission` 仅有 `install.sql` 种子数据(gid 0/1/2/4 的少数键,不含免审键)。修复后首次进入设置页时,免审项仍会显示为「勾选」(来自 `bbs_group` 回退值 1),属预期;**保存一次**后即写入 `group_permission` 并与显示一致。如需一次性同步,可在 `lib/UpgradeService.php` 增加一个幂等迁移:把 `bbs_group.allow_direct_*` 逐组 upsert 进 `group_permission`(可选,非必须)。
3. **清缓存**:`PermissionService` 无持久缓存(`tableExists()` 的 `static $exists` 仅请求内生效),本次问题与缓存无关,无需清缓存;改完代码需清理 `tmp/` 编译产物(`_include()` 对 `lib/` 类文件编译缓存)。
---
## 附:关键文件定位
| 文件 | 位置 | 角色 |
|---|---|---|
| `lib/PermissionService.php` | 224-233 行(**228 行**) | 根因:`tableExists()` 错误配置键 |
| `lib/PermissionService.php` | 152-179 / 120-144 / 241-248 行 | `updatePermissions()` / `getPermissions()` / `getPermissionValue()` 受影响链路 |
| `admin/route/group.php` | 132-142 / 167-174 行 | 第一段写 `bbs_group`(正常)/第二段写 `group_permission`(失效) |
| `admin/view/htm/group_update.htm` | 64-70 行 | 免审复选框渲染(数据源 `$saved_perms` = `getPermissions()`) |
| `lib/security/AuditService.php` | 46 / 77 行 | 发帖 / 回帖运行时审核判断 |
| `api/v1/user.php` | 447 行 | 资料(昵称)免审判断 |
| `xiunophp/db.func.php` | 510-526 行 | 正确的 `db_check_table_exists()` / `db_check_column_exists()`(参照实现) |
| `conf/conf.php` | db.mysql.master.tablepre / db.pdo_mysql.master.tablepre | 真实前缀 `xiunox_` |
Bug反馈
- 程序版本:
- 1.1.7
- PHP版本:
- php8.4
- MySql:
- 8.0
- 复现步骤:
- 后台管理编辑用户组调整审核权限时,取消勾选后点击保存,页面自动刷新,依然默认勾选
- 预期结果:
-
如果不按照提示进行填写,复制粘贴相同信息,将会被删除并扣除金币。
管理员标记为「已修复」,奖励 +5 金币







