【使用教程】XIUNO 封禁系统与 IP 黑名单使用指南 [Copy Link]

管理员组

封禁系统与 IP 黑名单使用指南

面向站长、管理员、版主与插件开发者。技术实现细节请参阅 XiunoX 用户封禁系统 。

一、功能介绍

1.1 用户封禁

针对恶意用户(小黑子)的 4 档状态管理:

状态

中文

能否登录

能否浏览

能否发帖/回帖

能否改密/找密

0

正常

✅

✅

✅

✅

1

禁言

✅

✅

❌

✅

2

禁止访问

❌

❌

❌

✅

3

锁定

❌

❌

❌

❌

核心特性:

  • 到期自动解封:访问时自动检查 banned_until,过期会重置封禁字段(不需要 cron 定时任务)

  • 永久封禁:时长选「永久」时 banned_until = 9999999999(约 2286 年),永不过期

  • 场景隔离:禁言用户仍可浏览和登录,仅在发帖/回帖入口被拦截

  • 内容隐藏:被封用户发布的帖子/回帖对普通用户隐藏,显示占位提示「该用户被关禁闭,内容被隐藏」;管理员和作者本人可见原始内容;解封后内容自动恢复

  • 完整历史:每次封禁/解封/自动解封/清空内容都写入 bbs_user_ban_log,可在后台查看

  • 管理员豁免:gid=1,2 的管理员组不可被封禁,管理员自己也不能被封自己

1.2 IP 黑名单 / 白名单

针对恶意 IP 的访问控制:

  • 支持格式:

    • 单 IP:192.168.1.100

    • CIDR 网段:192.168.1.0/24

    • IP 范围:192.168.1.1-192.168.1.10

    • IPv4 / IPv6 均支持

  • 白名单优先:IP 同时在黑白名单时,白名单生效,允许访问

  • 过期时间:可设永久或指定过期时间戳,过期后自动失效(不主动清理,查询时跳过)

  • 覆盖入口:登录、注册、发主题三个入口会做 IP 检查

  • 存储方式:使用 kv_set 序列化存储,不依赖额外数据表(注:旧的 bbs_banned_ip 表已废弃,仅作历史数据兼容)

1.3 封禁公示页

  • 路由:/banned

  • 三栏布局(与首页一致)+ nav-tabs 切换

  • 当前封禁中 tab:显示 ban_type > 0 的用户列表,按封禁时间倒序,最多 50 条

  • 近期释放 tab:显示近 30 天解封的用户,最多 20 条

  • URL 支持 path 同步:/banned / /banned-current / /banned-recent

  • 受 ban_show_public_list 配置控制,关闭时返回 404

  • 默认开启(注:Xiuno conf 加载机制详见 user-ban-system.md)

1.4 内容清空

针对需要保留账号但清空所有内容的场景:

  • 删除所有回帖

  • 删除主题索引(mythread 表,不删 thread 表主题记录)

  • 删除附件

  • 删除通知

  • 重置 threads / posts 计数为 0

  • 不删账号,用户主页显示「该用户已被管理员清空内容」提示


二、管理员使用教程

2.1 部署前的升级

部署后必须到 /admin/?upgrade.htm 执行「用户封禁系统」升级项,会自动:

  1. 为 bbs_user 表添加 4 个字段(ban_type / ban_reason / ban_admin_uid / ban_time)

  2. 创建 bbs_user_ban_log 表(封禁历史)

  3. 为已存在用户初始化 ban_type=0

不执行升级会导致封禁功能不可用,后台会报「Unknown column 'ban_type'」错误。

2.2 后台入口

后台侧栏「其他 → 安全设置」下,包含 8 个子 tab:

Tab

URL

用途

发帖限制

?security-post_limit.htm

发帖/回帖间隔、字数限制

账号安全

?security-account.htm

密码强度、注册间隔

内容权限

?security-content.htm

编辑/删除权限

其他设置

?security-other.htm

软删除、签名修改限制

验证码

?security-captcha.htm

验证码配置

敏感词

?security-words.htm

敏感词过滤

IP 黑名单

?banned_ip-list.htm

IP 黑白名单管理

封禁用户

?banned_user-list.htm

当前封禁用户列表/解封

2.3 封禁用户流程

方式 1:从用户列表进入

  1. 后台「用户管理 → 用户列表」

  2. 找到目标用户,点击「编辑」

  3. 在「封禁设置」区域:

    • 选择封禁类型(禁言 / 禁止访问 / 锁定)

    • 选择时长(1天 / 3天 / 7天 / 30天 / 永久 / 自定义)

    • 填写封禁原因(必填,会显示给用户)

  4. 提交即可

方式 2:从封禁用户 tab 进入(推荐批量管理)

  1. 后台「安全设置 → 封禁用户」

  2. 顶部搜索框支持:

    • 用户名模糊搜索:输入部分用户名,LIKE 匹配

    • UID 精确搜索:输入纯数字按 UID 精确匹配

  3. 列表中可看到封禁类型、状态、剩余时间、原因

  4. 点击「解封」按钮即可解封(会询问确认,调用 UserBanService::unban())

方式 3:从帖子页直接封禁(管理员/版主)

  1. 帖子详情页,作者卡片位置有「封禁」按钮

  2. 点击弹出封禁 Modal

  3. 选择类型和时长后提交

方式 4:举报联动

  1. 后台「举报管理」处理举报时

  2. 选择「封禁用户」会自动禁言 7 天

  3. 选择「删除并封禁」会先删内容再封禁

2.4 IP 黑名单使用

添加 IP

  1. 后台「安全设置 → IP 黑名单」

  2. 选择类型:单 IP / CIDR / 范围

  3. 填入 IP(如 192.168.1.100 或 192.168.1.0/24 或 192.168.1.1-192.168.1.10)

  4. 填写备注(可选)

  5. 选择过期时间(永久或指定日期)

  6. 提交即可

IP 命中后的行为

  • 命中黑名单的 IP 在以下入口被拦截:

    • 登录(route/user.php login)

    • 注册(route/user.php create)

    • 发主题(route/thread.php create)

  • 命中时返回提示「您的 IP 已被加入黑名单」并中止请求

  • 命中白名单的 IP 即使在黑名单中也允许访问(白名单优先)

2.5 版主权限限制

版主(gid=3,4,5)封禁时有严格的三重校验:

  1. 类型限制:只能禁言(ban_type=1),不能禁止访问或锁定

  2. 不可永久:duration 不能为 0

  3. 时长限制:必须在 1-7 天之间(86400 ~ 604800 秒)

管理员(gid=1,2)无上述限制,可永久封禁和锁定。

2.6 公示页控制

  • 配置项:ban_show_public_list

  • 位置:conf/conf.php

  • 值:1 开启(默认)/ 0 关闭

  • 关闭后访问 /banned 返回 404

注意:Xiuno 只加载 conf/conf.php,不合并 conf.default.php。旧站点若 conf.php 没有此项,代码用 isset() && empty() 双重判断,默认视为开启。


三、插件开发者指南

3.1 调用前必做的依赖加载

生产环境(DEBUG=0)走 tmp/model.min.php 合并加载,类加载顺序不可预测。开发环境(DEBUG=1)因其他插件先执行而「碰巧能用」,但生产环境会抛 Class not found 致命错误。

正确写法(所有调用 UserBanService / IpBlacklistService 的插件代码):

// 调用 UserBanService 前
if(!class_exists('UserBanService')) {
    include_once APP_PATH.'lib/UserBanService.php';
}

// 调用 IpBlacklistService 前
if(!class_exists('IpBlacklistService')) {
    include_once APP_PATH.'lib/security/IpBlacklistService.php';
}

错误写法(生产环境会崩):

// ❌ 直接访问静态成员
if(UserBanService::ADMIN_GIDS) { ... }  // 若类未加载,这里就崩溃

// ❌ 假设类已被其他代码加载
UserBanService::ban($uid, 1, 86400, 'spam', $adminUid);

3.2 在插件中封禁用户

<?php
!defined('DEBUG') AND exit('Access Denied');

// 1. 加载依赖
if(!class_exists('UserBanService')) {
    include_once APP_PATH.'lib/UserBanService.php';
}

// 2. 检查 CSRF
CsrfService::check();

// 3. 接收参数(注意:reason 等文本参数第 3 个参数传 FALSE 关闭 htmlspecialchars)
$target_uid = intval(param('uid'));
$reason = param('reason', '', FALSE);

// 4. 调用核心封禁服务(禁言 3 天)
$result = UserBanService::ban(
    $target_uid,
    UserBanService::BAN_TYPE_SILENCE,
    86400 * 3,  // 3 天
    $reason,
    $uid         // 当前登录管理员 uid(全局变量)
);

if($result['code'] != 0) {
    message(-1, $result['message']);
}
message(0, lang('user_ban_success'));

3.3 在插件中检查 IP

// 加载依赖
if(!class_exists('IpBlacklistService')) {
    include_once APP_PATH.'lib/security/IpBlacklistService.php';
}

// 检查当前访客 IP 是否在黑名单
global $ip;
if(IpBlacklistService::is_blacklisted($ip)) {
    message(-1, lang('user_ban_ip_banned'));
}

// 也可以检查任意 IP
if(IpBlacklistService::is_blacklisted('192.168.1.100')) {
    // 命中
}

// 检查是否在白名单
if(IpBlacklistService::is_whitelisted($ip)) {
    // 白名单优先,跳过检查
}

3.4 在插件中添加 IP 到黑名单

if(!class_exists('IpBlacklistService')) {
    include_once APP_PATH.'lib/security/IpBlacklistService.php';
}

// 添加单 IP,永久,备注「广告 IP」
IpBlacklistService::add_blacklist_entry(
    '192.168.1.100',
    '广告 IP',
    0,           // 0 = 永久
    $uid         // 操作管理员 uid
);

// 添加 CIDR 网段,7 天后过期
IpBlacklistService::add_blacklist_entry(
    '192.168.1.0/24',
    '段封禁',
    time() + 86400 * 7,  // 过期时间戳
    $uid
);

// 添加 IP 范围
IpBlacklistService::add_blacklist_entry(
    '192.168.1.1-192.168.1.10',
    '范围封禁',
    0,
    $uid
);

// 从黑名单移除(参数必须与添加时完全一致)
IpBlacklistService::remove_from_blacklist('192.168.1.100');

// 获取黑名单分页列表(自动过滤已过期)
$list = IpBlacklistService::get_blacklist_page(1, 50, true);
$total = IpBlacklistService::count_blacklist(true);

// 清理所有已过期条目(可选,定期调用释放空间)
$cleaned = IpBlacklistService::purge_expired_blacklist();

3.5 监听封禁事件

通过 XnEvent 机制接入封禁流程。文件位置:lib/XnEvent.php。

// plugin/my_plugin/hook/model_inc_start.php
<?php exit;

// 加载依赖
if(!class_exists('UserBanService')) {
    include_once APP_PATH.'lib/UserBanService.php';
}
if(!class_exists('XnEvent')) {
    include_once APP_PATH.'lib/XnEvent.php';
}

// 监听封禁后事件(如审计日志)
XnEvent::on('UserBanService.afterBan', 'my_plugin', function(&$args) {
    // $args 含:uid / banType / duration / reason / adminUid / bannedUntil
    db_insert('my_plugin_audit_log', array(
        'uid'        => $args['uid'],
        'action'     => 'ban',
        'ban_type'   => $args['banType'],
        'duration'   => $args['duration'],
        'reason'     => $args['reason'],
        'admin_uid'  => $args['adminUid'],
        'expire_at'  => $args['bannedUntil'],
        'create_time'=> time(),
    ));
});

// 监听解封后事件(如清理插件数据)
XnEvent::on('UserBanService.afterUnban', 'my_plugin', function(&$args) {
    // $args 含:uid / reason / adminUid
    db_delete('my_plugin_ban_data', array('uid' => $args['uid']));
});

可用事件清单:

事件名

触发时机

可修改参数

UserBanService.beforeBan

封禁前

banType / duration / reason(引用,可修改)

UserBanService.afterBan

封禁后

只读,含 bannedUntil

UserBanService.beforeUnban

解封前

reason(引用,可修改)

UserBanService.afterUnban

解封后

只读

UserBanService.beforeClearContent

清空前

uid / adminUid

UserBanService.afterClearContent

清空后

只读

UserBanService.bannedListDisplay

公示页渲染时

current_list / recent_list(引用,可修改)

3.6 修改封禁时长(风控插件示例)

// plugin/risk_control/hook/model_inc_start.php
<?php exit;
if(!class_exists('XnEvent')) {
    include_once APP_PATH.'lib/XnEvent.php';
}

XnEvent::on('UserBanService.beforeBan', 'risk_control', function(&$args) {
    // 检查用户风险等级
    $risk = RiskControlService::getRiskLevel($args['uid']);
    if($risk === 'high' && $args['duration'] > 0 && $args['duration'] < 86400 * 30) {
        // 高风险用户:自动延长到 30 天
        $args['duration'] = 86400 * 30;
        $args['reason'] .= '(风控自动延长至30天)';
    }
});

3.7 修改封禁公示页列表

// plugin/my_plugin/hook/model_inc_start.php
<?php exit;
if(!class_exists('XnEvent')) {
    include_once APP_PATH.'lib/XnEvent.php';
}

XnEvent::on('UserBanService.bannedListDisplay', 'my_plugin', function(&$args) {
    // $args['current_list'] 当前封禁列表(引用)
    // $args['recent_list'] 近期释放列表(引用)
    foreach($args['current_list'] as &$row) {
        $row['plugin_badge'] = '⚠️';  // 添加插件徽章
    }
});

3.8 自定义 IP 检查(外接 IP 信誉库)

// plugin/ip_reputation/hook/banned_ip_check.php
<?php exit;
// 此 hook 在 banned_ip_check() 落地后调用
// 如需更严格的检查,可直接调用 IpBlacklistService::is_blacklisted() 或自建查询

$reputation = IpReputationService::query($ip);
if($reputation['score'] < -50) {
    // 自动加入本地黑名单
    if(!class_exists('IpBlacklistService')) {
        include_once APP_PATH.'lib/security/IpBlacklistService.php';
    }
    IpBlacklistService::add_blacklist_entry(
        $ip, $ip,
        'IP信誉库自动拦截',
        time() + 86400 * 7,
        0  // 系统
    );
    message(-1, lang('user_ban_ip_banned'));
}

3.9 插件卸载时清理事件监听器

重要:插件卸载时必须清理事件监听器,否则卸载后回调仍会被触发,导致 Class not found 错误。

// plugin/my_plugin/uninstall.php
<?php
!defined('DEBUG') AND exit('Access Denied');
if(!class_exists('XnEvent')) {
    include_once APP_PATH.'lib/XnEvent.php';
}

// 移除本插件注册的所有事件监听器
XnEvent::off(null, 'my_plugin');

3.10 插件可用的 PHP hook 点

Hook 文件

位置

用途

user_ban_check.php

route/user.php / route/thread.php / route/post.php / route/my.php / index.inc.php

自定义封禁检查(如第三方风控插件判定)

banned_ip_check.php

route/user.php login/create / route/thread.php create

自定义 IP 检查(如外接 IP 信誉库)

banned_list_display.php

route/banned.php

修改封禁公示页列表数据

3.11 插件可用的模板 hook

Hook 名

模板位置

用途

banned_start.htm

banned.htm 顶部

公示页头部注入

banned_left_before.htm

banned.htm 中栏顶部

公示页中栏顶部注入

banned_tab_item_after.htm

banned.htm nav-tabs 末尾

添加自定义 tab

banned_current_body_before.htm

当前封禁 tab 内容前

注入内容

banned_current_body_after.htm

当前封禁 tab 内容后

注入内容

banned_recent_body_before.htm

近期释放 tab 内容前

注入内容

banned_recent_body_after.htm

近期释放 tab 内容后

注入内容

banned_current_item_start.htm

列表项开始

修改单项显示

banned_current_item_end.htm

列表项结束

修改单项显示

banned_recent_item_start.htm

列表项开始

修改单项显示

banned_recent_item_end.htm

列表项结束

修改单项显示

banned_bottom.htm

公示页底部

注入内容

banned_end.htm

公示页结尾

注入内容

banned_js.htm

公示页 JS 之后

注入自定义脚本


四、API 速查表

4.1 UserBanService 静态方法

方法

用途

返回值

UserBanService::ban($uid, $banType, $duration, $reason, $adminUid)

封禁用户

['code'=>0成功, 'message'=>错误]

UserBanService::unban($uid, $adminUid, $reason='')

解封用户

['code'=>0, 'message'=>]

UserBanService::checkBan($uid)

检查状态(含自动解封)

['banned'=>bool, 'ban_type'=>, 'ban_reason'=>, 'expire_time'=>, 'expire_formatted'=>]

UserBanService::checkBanByScene($uid, $scene)

按场景检查

['allowed'=>bool, 'message'=>]

UserBanService::clearContent($uid, $adminUid)

清空内容

['code'=>0, 'message'=>]

UserBanService::getBanStatus($uid)

获取格式化状态

['ban_type', 'ban_reason', 'ban_time', 'banned_until', 'expire_formatted', 'status_label', 'status_color']

UserBanService::getBanTypeLabel($banType)

类型标签

['label'=>, 'color'=>]

UserBanService::formatDuration($duration)

时长格式化

"7天" / "永久" / "3小时"

UserBanService::formatExpireTime($banned_until)

过期时间格式化

"2026-07-09 12:00:00" / "永久"

4.2 UserBanService 常量

常量

值

说明

BAN_TYPE_NORMAL

0

正常

BAN_TYPE_SILENCE

1

禁言

BAN_TYPE_BAN_ACCESS

2

禁止访问

BAN_TYPE_LOCK

3

锁定

PERMANENT_BAN

9999999999

永久封禁时间戳(约 2286 年)

ADMIN_GIDS

[1, 2]

管理员组 gid

4.3 场景检查规则

Scene

拒绝的 ban_type

用途

login

2, 3

禁止访问 / 锁定 不能登录

browse

2, 3

禁止访问 / 锁定 不能浏览

post

1, 2, 3

禁言及以上不能发帖回帖编辑

password

3

锁定 不能改密找密

4.4 IpBlacklistService 静态方法

方法

用途

IpBlacklistService::is_blacklisted(string $ip): bool

检查 IP 是否在黑名单

IpBlacklistService::is_whitelisted(string $ip): bool

检查 IP 是否在白名单

IpBlacklistService::add_blacklist_entry(string $ip, string $remark='', int $expire_time=0, int $admin_uid=0): bool

添加黑名单

IpBlacklistService::add_whitelist_entry(string $ip, string $remark='', int $expire_time=0, int $admin_uid=0): bool

添加白名单

IpBlacklistService::remove_from_blacklist(string $ip): bool

从黑名单移除

IpBlacklistService::remove_from_whitelist(string $ip): bool

从白名单移除

IpBlacklistService::get_blacklist(): array

获取全部黑名单

IpBlacklistService::get_whitelist(): array

获取全部白名单

IpBlacklistService::get_blacklist_page(int $page=1, int $pagesize=50, bool $exclude_expired=false): array

分页获取黑名单

IpBlacklistService::count_blacklist(bool $exclude_expired=false): int

黑名单计数

IpBlacklistService::purge_expired_blacklist(): int

清理已过期条目,返回清理数量


五、测试清单

部署封禁系统后建议测试以下场景:

5.1 基础功能

  • 后台编辑用户 → 选择禁言 7 天 → 提交 → 用户登录前台发帖被拒

  • 禁言用户可登录浏览,但发帖/回帖/编辑被拒

  • 禁止访问用户登录被拒,浏览被拒

  • 锁定用户登录、改密、找密全部被拒

  • 设置封禁时长 1 分钟 → 等待过期 → 再次访问 → 自动解封

  • 永久封禁 → banned_until=9999999999 → 永不解封

5.2 权限与豁免

  • 尝试封禁 gid=1,2 用户 → 拒绝

  • 管理员尝试封禁自己 → 拒绝

  • 管理员访问被封 IP 不受影响

  • 版主尝试永久封禁 → 拒绝

  • 版主尝试禁言 8 天 → 拒绝

  • 版主尝试禁止访问 → 拒绝

5.3 内容显示

  • 被封用户发的帖子,普通用户看到占位提示

  • 管理员看到原始内容

  • 被封用户本人看到自己的内容

  • 解封后帖子内容自动恢复显示

  • 清空内容后用户主页显示「已被管理员清空内容」

5.4 IP 黑名单

  • 添加单 IP → 用该 IP 访问 → 登录/注册/发帖三个入口全部被拒

  • 添加 CIDR 网段 → 该网段 IP 被拒

  • 添加 IP 范围 → 范围内 IP 被拒

  • 添加白名单 IP → 同时在黑白名单 → 允许访问

  • 设置过期时间 → 过期后自动失效

5.5 公示页

  • 开启 ban_show_public_list → 访问 /banned 看到三栏布局

  • 切换 tab → URL 同步变为 /banned-current / /banned-recent

  • 刷新 /banned/recent → 直接显示「近期释放」tab

  • 浏览器前进/后退 → tab 正确还原

  • 关闭 ban_show_public_list → 访问 /banned 返回 404

5.6 后台管理

  • 后台「安全设置 → 封禁用户」tab 显示当前封禁用户列表

  • 用户名模糊搜索正常

  • UID 精确搜索正常

  • 点击解封按钮 → 确认 → 用户从列表消失

  • 后台「安全设置 → IP 黑名单」tab 显示 IP 列表

  • 添加/删除 IP 正常

  • 后台侧栏高亮正确(封禁用户/IP 黑名单都高亮「安全设置」)

5.7 生产环境

  • DEBUG=0 模式下访问各入口,确认 UserBanService 类加载正常

  • DEBUG=0 模式下访问 /banned,确认无 Class not found 错误

  • 多次封禁解封后,后台 user-ban_log-{uid}.htm 显示完整历史


六、常见问题

Q1: 封禁后用户还能登录怎么办?

检查封禁类型:

  • 禁言(type=1)允许登录,仅拦截发帖/回帖/编辑

  • 禁止访问(type=2)和锁定(type=3)才会拒绝登录

Q2: 封禁过期后用户状态没恢复?

系统在用户访问时自动检查 banned_until 并自动解封,不需要 cron。如果用户一直不访问,状态会保持到下次访问时才解封。可手动在后台「封禁用户」tab 解封。

Q3: 封禁公示页 404?

检查 conf/conf.php 中是否有 ban_show_public_list 配置项:

  • 没有此项 → 默认开启(代码用 isset() && empty() 判断)

  • 设为 0 → 关闭,访问返回 404

  • 设为 1 → 开启

Q4: 插件中调用 UserBanService 报 Class not found?

生产环境(DEBUG=0)走 tmp/model.min.php 合并加载,类加载顺序不可预测。必须在调用前 include_once:

if(!class_exists('UserBanService')) {
    include_once APP_PATH.'lib/UserBanService.php';
}

Q5: IP 黑名单不生效?

检查覆盖入口:

  • 登录(route/user.php login)

  • 注册(route/user.php create)

  • 发主题(route/thread.php create)

  • 回帖/编辑不在覆盖范围内(仅拦截发主题入口)

如果需要更多入口拦截,可通过 banned_ip_check.php hook 自行扩展。

Q6: 版主封禁时报错「权限不足」?

版主封禁有严格三重校验:

  1. 只能禁言(type=1)

  2. 不能永久(duration≠0)

  3. 时长必须在 1-7 天(86400-604800 秒)

如果需要更长时长或禁止访问,需管理员操作。

Q7: 清空内容后帖子还在?

清空内容只删除:

  • 回帖(post 表)

  • 主题索引(mythread 表,不删 thread 表主题记录)

  • 附件

  • 通知

  • 重置计数

主题记录本身不会删除,这是已知天花板(避免破坏帖子楼层引用)。如需彻底删除主题,需在后台「主题管理」单独删除。

Q8: 如何在插件中添加自定义封禁检查?

通过 user_ban_check.php hook:

// plugin/my_plugin/hook/user_ban_check.php
<?php exit;
// 此 hook 在核心封禁检查之后调用
// $uid 是当前用户 uid
if(my_plugin_custom_check($uid)) {
    message(-1, '您的账号存在风险,已被限制');
}


New Post

Please log in to reply Login

uid:1 管理员组
Follow
Posts 12
Comments 0
Followers 0
Following 0
Publish
Table of Contents
【使用教程】XIUNO 封禁系统与 IP 黑名单使用指南
This Ad Slot Available
This Ad Slot Available
This Ad Slot Available
Ad Available
Ad Available
Ad Available
Ad Available
Ad Available
Ad Available