精华 【使用教程】XIUNO 封禁系统与 IP 黑名单使用指南

贰先生 管理 1小时前

封禁系统与 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.100192.168.1.0/24192.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: 插件中调用 UserBanServiceClass 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, '您的账号存在风险,已被限制');
}

最新回复
  • 这个帖子是长文,电脑端鼠标滚动到3.9章节左右会自动回到上方,页面滚动不下去
    46分钟前

请先登录后再回复 登录

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

扫码手机打开本帖