等级系统(levels)建议征集。热
昨天下午+晚上3个40余轮对话共消耗4000+积分
精细打造(未完工),刚又适配到9.1。我的想法是后期我制作的所有和权限相关插件都将基于此插件制作。(本插件扩展 API(私有钩子)),但个人的能力和想法有限,征集下大家的想法一起来完善。
- 积分等级体系:等级为积分的纯函数(从不落库),达标自动晋升、跌破自动回落;默认档位 LV0=0 / LV1=30 / LV2=100 / LV3=300 / LV4=1000,后台可任意增删改(名称唯一、门槛递增、首档自动归零、上限 20 档)。
- 用户状态域(单一状态链):骗子 > 禁访 > 禁言 > 停用 > 管理 > 特殊 > LV,一人同一时刻仅呈现一种状态;骗子/禁访互斥(入组承接对方 prev_flag,保证恢复链不中断)。
- 等级/状态徽章:帖子详情页与列表页作者旁显示徽章;三色(边框/底色/文字)后台可配,仅接受
#RRGGBB白名单色值。 - 版块等级门槛:按版块独立配置 查看/发帖/回帖 三项 LV 门槛;版块页与详情页直接拦截,混合列表(首页/搜索/个人主页)逐行隐藏;管理组与特殊组直通。
- 停用组只读:
user.can_speak返回自定义提示语,保留浏览权限但禁止发言。 - 内容遮罩:组级开关,开启后该组成员历史主贴/回帖正文替换为禁止图标(仅改渲染,移出组即恢复)。
- 个性签名:Markdown 渲染(复用内核
markdown_html,自带 XSS 防护)、仅登录可见、骗子隐藏、两行高截断;个人资料页编辑面板带实时字数统计与防抖 AJAX 预览;红区仅埋占位符,page.before_render整页一条 IN 查询批量回填;AJAX 新楼层路径就地单作者渲染。 - 升级/降级站内通知:
user.points_changedO(1) 门槛比对,措辞中性,各自独立开关。 - 前台快捷操作:主题页管理员行内 禁言(含时长档位)/解禁/禁访/解禁访/骗子/移出骗子,统一理由弹窗(每页一次),
levels_op路由白名单分发并全量复审权限。 - 禁言到期自动解除:
app.boot惰性清理,60 秒原子节流(条件 UPDATE 抢占 + rowCount 认领),永久禁言expires_at=0不误判。 - 成员管理:后台五区(等级与徽章/成员管理/版块限制/操作记录/设置),用户名联想搜索、按组筛选、分页;标志位(is_banned/is_muted)写入与原值恢复全在事务内。
- 审计日志:每次状态变更一行(含到期自动解禁,operator=0),理由必填;后台按动作筛选、CSV 导出(id 游标分块 + UTF-8 BOM + 公式注入防护)、保留期自动清理(1/50 概率防写放大)、一键清空。
- 公开管理日志:可开关的前台页,仅公开 禁言/禁访/骗子 类动作(停用/特殊不公开),自然语言事件句。
- 侧栏等级卡片与 /levels 等级页:当前徽章、积分、升级进度条、下一等级差距、等级一览表。
- 扩展 API(私有钩子):
levels.query(查询用户等级/状态)、levels.mute_set/mute_clear/ban_set/ban_clear(供其他插件编程调用成员管理)。 - 卸载语义:保留数据时一切原样(标志位与成员关系继续生效于内核);删除数据时先按 prev_flag 恢复全部标志位再清表。


主楼1. 设计理念
- 等级是积分的纯函数:等级从不落库、从不迁移,
levels_of_points()实时按门槛计算,后台改档位即刻全站生效。没有"升级任务"、没有状态同步、没有边界 bug——积分变,等级变。 - 单一状态链:一个用户同一时刻只呈现一种状态,优先级
骗子 > 禁访 > 禁言 > 停用 > 管理 > 特殊 > LV。命中高优先状态即完全遮蔽低优先展示(如骗子不再显示 LV),杜绝"既是骗子又是 LV3"的观感混乱。 - 复用内核标志位:禁言/禁访不另建权限体系,直接写内核
is_muted/is_banned(写入时记录原值 prev_flag,移出组时精确恢复);内核自身的封禁判断、状态标签、登录拦截全部自动生效,权限模型随核心演进零漂移。 - 红区零 DB:渲染循环 Hook(before/after_render、content_after、can_speak、topic.actions)内只读请求级缓存与纯函数——三张业务表(成员/组/规则)在写入时镜像为 JSON 进
app_settings,读取走settings_cache(每请求一次全量设置查询,零额外开销);行内用户字段(group_id/points/is_banned/is_muted)由核心attach_users预载。 - 占位符回填:签名在红区只埋 HTML 注释占位符(含每请求随机 token),
page.before_render收尾时一条 IN 查询批量取全部签名并正则回填,替换 O(n) 行渲染为 O(1) 次查询。 - 服务端兜底,前端仅交互:JS 只负责弹窗显隐、字数统计、防抖预览、后台表格行克隆;
levels_op路由统一need_login + require_post+ can_manage + 目标保护复审,被绕过不构成安全漏洞。
2. 权限模型
能力 判定 等级门槛豁免 管理组成员(uid=1 或用户组 allow_manage=1)与特殊组成员直通所有版块门槛 版块 查看/发帖/回帖 版块规则下标 ≤ 用户等级下标;发帖在 topic.before_save、回帖在reply.before_save(一次 forum_id 查询)、查看在版块页topic.index_data.loaded(err 拦截)与详情页topic.before_render。1.0.1 起前置提示:回帖表单在详情页即被替换为提示盒(reply.form_extra+page.before_render,等级不足无从输入);发帖页经topic.form_extra注入提示条 + 版块门槛映射,JS 按所选版块联动禁用提交按钮(无 JS 降级为提交时拦截)混合列表过滤 topic.before_render(list 上下文)标记__levels_list_hidden+topic.after_render返回空串清行(核心不回收 index_data.loaded 的返回值,不能在该钩子内改 rows)停用组只读 user.can_speak返回提示字符串(内核非 true 即拒绝发言),浏览权限保留;主题页回帖框文案由page.before_render按停用状态替换(内核can_speak()只返回 bool,硬编码「当前用户禁止发言」,插件的字符串提示仅在提交时经need_speak呈现)前台快捷操作 can_manage()渲染 +front_ops_enabled开关;levels_op路由服务端复审操作目标保护 levels_row_can_target()(内存版,红区):uid=1 绝对保护;管理组成员仅创始人(或allow_admin_targets开关开启)可操作。写路径用查库版levels_can_target()复审后台五区 / CSV / 用户联想 need_admin()公开日志 所有登录用户; public_logs_enabled开关控制,仅公开 禁言/禁访/骗子 动作签名 仅登录可见( uid());骗子/内容遮罩作者隐藏(1.3.8);停用/禁言正常显示3. 架构与模块划分
app/plugins/levels/plugin.php(单文件插件,约 2390 行) ├── 配置 levels_config / levels_default_defs / │ levels_defs / levels_color static 请求级缓存;门槛排序去重、 │ 首档归零、20 档上限;色值 #RRGGBB 白名单 ├── 运行时快照 levels_runtime / members_of / group_cfg / │ rule_of / cache_rebuild 三张表 JSON 镜像进 app_settings; │ 写路径变更后必调 rebuild 刷新镜像与快照 ├── app.boot levels_boot / expire_mutes 预载快照 + 禁言到期惰性清理 │ (60 秒条件 UPDATE 原子节流 + rowCount 认领) ├── 状态链解析 levels_row_state / row_is_manage / │ me_bypass / row_can_target 纯内存判定;返回最高优先状态 key ├── 展示 levels_badge_html / row_badge / │ inject_badge / apply_mask 徽章注入(位置可选:信息行开头/末尾/作者名后, │ 未命中逐级回退;列表行固定信息行开头, │ 受列表页开关控制);遮罩替换 post-content ├── 门禁 levels_gate_allows / gate_err / │ topic_before_save / reply_before_save / │ index_loaded / topic_before_render / │ topic_form_extra / reply_form_extra 三项门槛统一错误语; │ 混合列表标记 + after_render 清行; │ 1.0.1 发帖提示条 + 回帖表单前置替换 ├── 停用组 levels_can_speak user.can_speak 字符串提示 ├── 签名 signature_html / allowed / placeholder / │ page_render / profile_form / │ signature_save / signature_preview 占位符批量回填 + AJAX 单楼层就地渲染 + │ 防抖预览(与最终渲染同一函数) ├── 通知 levels_points_changed user.points_changed + points_manager.points_changed 双事件 O(1) 门槛比对 ├── 成员管理 member_set / member_remove / │ can_target 事务内标志位写入/原值恢复; │ 骗子↔禁访互斥承接 prev_flag ├── 审计 levels_log / log_sentence / │ log_actions / log_public_actions 每次变更一行;1/50 概率清理过期; │ 自然语言事件句供公开日志 ├── 扩展 API levels_hook_query / mute_set / │ mute_clear / ban_set / ban_clear 私有钩子,供其他插件 fire()/hook() 调用 ├── 前台快捷操作 quickops_html / op_icon / inject_quickops / │ modal_html / op_route / topic_actions 注入位置统一为「引用回复」之前; │ 入组三键图标化;弹窗每页一次; │ 目标保护内存判定;路由白名单分发 + 服务端复审 ├── 页面 levels_page / public_logs_page / │ logs_download / search_users /levels 等级页、公开日志页(分页)、 │ CSV(id 游标分块)、用户联想 ├── 后台五区 levels_admin_page + 5 个子页渲染/保存 等级与徽章 / 成员管理 / 版块限制 / │ 操作记录 / 设置;POST 白名单分发 ├── 安装/卸载 levels_install / levels_uninstall 五张自有表 + 组配置种子(幂等); │ 卸载先恢复标志位再清表,运行时标记 │ (节流戳)随镜像一并复位(1.3.4) └── 资产 levels_css / levels_js levels- 前缀;JS 具名 IIFE 仅交互4. 数据模型
表 用途 关键列 plugin_levels_groups六个固定组的徽章三色与内容屏蔽开关 group_key(PK)、name、border/bg/text、mask plugin_levels_members手动组成员(每用户每组一行) UNIQUE(user_id,group_key)、reason、operator_id、expires_at(禁言专用,0=永久)、prev_flag(内核标志位原值) plugin_levels_forum_rules版块三项 LV 门槛(下标,0=不限制) forum_id(PK)、view_level/post_level/reply_level plugin_levels_signatures个性签名 user_id(PK)、content、updated_at plugin_levels_logs审计日志 action(11 种)、operator_id(0=系统/到期)、target_id、detail(用户名[:到期戳])、reason、created_at 另有
app_settings三个 JSON 镜像键(plugin_levels_members_json/_groups_json/_rules_json)与节流键plugin_levels_last_expire_check。5. 关键机制说明
5.1 等级纯函数
function levels_of_points(int $points): int { $li = 0; foreach (levels_defs() as $i => $d) { if ($points >= $d['points']) $li = $i; else break; } return $li; }levels_defs()保证门槛严格递增,因此首档命中即短路。升/降级通知同样基于新旧两次纯函数结果的比对,无任何存储状态。5.2 状态链与内核标志位的分工
- 内核标志位(is_banned/is_muted)是权限事实:内核登录拦截、发言拦截、状态标签全部依赖它;
- 本插件的成员表是管理事实:谁被谁为何移入哪个组、何时到期、标志位原值是多少;
- 状态链解析在渲染层合并两者:骗子组成员即使带着 is_banned=1 也只显示骗子徽章(before_render 压掉渲染副本的标志位,不动库)。
5.2.1 单一状态强制(1.0.2 / 1.2.3)
四个处罚组(骗子/禁访/禁言/停用)与特殊组在数据层互斥:
levels_member_set入任一组前,事务内先移出该用户的对方集合组成员关系——入处罚组时清除其他处罚组 + 特殊组;入特殊组时清除所有处罚组。处罚组按各自 prev_flag 恢复标志位(停用/特殊无内核标志位直接删记录);新组的 prev_flag 承接恢复后的当前标志位,保证任意顶替顺序下恢复链不中断。每个被顶替组各记一行「移入X组自动解除」审计。这消除了"先禁言再停用 → is_muted 残留"以及"特殊组与骗子组共存"一类叠加态。manage 组是内核身份(group_id 的 allow_manage),不在本插件管理范围,不参与互斥。存量自愈(1.2.3)(已于 1.3.7 移除):levels_mutex_self_heal()曾用于修复 1.2.2 之前共存态的存量脏数据;新装环境互斥由数据层从源头保证,无迁移负担,纯死代码已清理。5.3 禁言到期惰性清理
继承 topic_manage 已验证的模式:
app.boot时从内存快照筛出已到期记录 → 条件 UPDATE 抢占 60 秒节流窗(并发仅一个请求真正执行)→ 逐用户事务内 rowCount 认领翻转 is_muted 并删成员行 → 审计一行 operator=0 → 重建镜像。无到期记录的请求零 DB 触碰。1.3.4 起审计与镜像重建只对事务内认领成功(tx()返回 bool)的 uid 执行——此前按事务外 SELECT 结果记日志,节流戳首次初始化的并发窗口内可能重复记录mute_expire。5.4 混合列表过滤(关键坑位)
核心
topic_index_data()对topic.index_data.loaded的返回值不回收(fire-and-forget),在该钩子里改写 rows 无效。正确通道是列表渲染链:topic_list_row()对每行先过topic.before_render(list 上下文),返回的 row 继续传入topic.after_render——因此 before_render 标记__levels_list_hidden、after_render 见标记返回空串即可整行清除,与 topic_read_groups 的隐藏机制同构。5.4.1 门槛前置提示(1.0.1)
提交时拦截(before_save)只保证安全,不保证体验——等级不足的用户会在辛苦编辑后才收到拒绝。前置提示分两处:
- 回帖(硬阻止):
reply.form_extra(核心仅在 can_speak 且组权限通过时触发,ctx 带 topic 行含 forum_id)以纯内存判定等级不足时置全局标记;levels_page_render据此把<form class="ajax-reply-form">整体替换为核心「无权限」同形态的reply-login-box disabled提示盒。用户无从输入,杜绝白写。访客/停用用户渲染的本来就是提示盒,不经过该钩子。 - 发帖(软提示 + 联动禁用):
topic.form_extra(新建/编辑均触发,ctx 带 topic 行)注入.levels-gate-hint提示条——服务端按表单初始 forum_id 预渲染文案并决定初始显隐;因版块下拉可切换(服务端无法预知最终选择),同时携带全版块发帖门槛映射data-levels-post-gates(仅含 post_level>0 的版块,JSON 经 h() 属性转义)。JS 从提示条closest('form')定位版块 select 与提交按钮:change 时按映射显隐提示并 disabled/恢复按钮,初始执行一次同步状态。无 JS 时降级为提交时拦截。 - 文案统一走
levels_gate_hint()(view/post/reply 三动词),与提交时levels_gate_err()完全一致;JS 端重建文案仅用于切换版块后的更新。
5.5 签名占位符回填
红区
content_after只输出<!--levels-{token}-{uid}-->(token 每请求随机,防内容伪造命中);page.before_render收集全部待回填 uid,一条 IN 查询取签名,正则回调替换(未设置者替换为空)。AJAX 新楼层片段不经 page.before_render,由reply.after_save记录作者 uid 后在 content_after 内就地单作者渲染(单行一次查询,可接受)。钩子链陷阱(1.0.6 修复):
topic.content_after是管道追加语义($value为先序插件已追加的内容,必须拼接返回),且核心hook()值替换 + 插件按 id 排序执行——id 较小的插件输出会被后续"整体替换"型消费者清掉(本插件曾被 topic_manage 旧版清掉主贴签名占位符)。挂接该钩子的插件一律$value . 追加内容返回。5.6 目标保护双通道
- 渲染层(红区):
levels_row_can_target($row)用行内字段(经 attach_users 预载)+group_by_id(settings 缓存)判定,零 DB; - 写路径:
levels_can_target($uid)查库复审——即便渲染层被绕过(如直接 POST),操作仍被拒绝; - 两通道共用
levels_row_uid()归一提取用户 ID:帖子/回帖行用user_id,用户行(me()/app_users)用id——这是 1.0.1 修复的关键缺陷(两类行结构不同,直接读user_id会恒得 0 导致全部操作被拒)。
5.7 用户名联想与理由选择(1.0.1 / 1.1.1)
- 后台成员添加:
data-levels-user-input输入防抖 200ms →levels_search_users(need_admin+_csrf,前缀 LIKE 转义 / 纯数字按 uid 精确)→ fixed 定位下拉(#id + 用户名),↑↓/Enter/Esc/外点交互,参照 topic_manage 成熟实现; - 理由选择(1.1.1 / 1.1.2 / 1.2.0,topic_manage 同构):预设理由 select +「自定义输入…」选项——选预设由 JS 回填隐藏字段
reason;选自定义经统一的reasonCustomVisible()显示输入框(必填、自动聚焦)并实时同步,显隐同时切换输入框自身的hidden与其包裹 label(前台弹窗 label>input 形态、后台裸 input 形态都覆盖——只切 label 会踩 1.1.2 修复的后台卡死缺陷)。三处统一:后台成员添加(入组类预设)、后台成员移出(解除类预设)、前台快捷操作弹窗(选项由 JS 按 op 后缀_clear/_remove从data-levels-reasonsJSON 重建)。预设理由后台「功能设置」可配(两组 textarea 每行一条,levels_preset_lines()归一化:trim、cut 60、去重、最多 30 条,空集回退levels_preset_defaults()内置默认)——一处配置三处生效。服务端无 JS 兜底:隐藏reason为
- 等级是积分的纯函数:等级从不落库、从不迁移,
太复杂了🙃
这其实是用户组功能增强;你可以参考 https://addon.dismall.com/library/admcp/user/user_usergroups.html
Discuz 从「单用户组」到「主用户组 + 多扩展用户组」为什么要加
早期 Discuz(CDB / Discuz! 2.x/3.x 时代):一个用户只能归属 1 个用户组(groupid)。
后来 X 系列引入extgroupids扩展用户组字段,支持一个用户同时挂多个用户组,主用户组决定基础身份,扩展用户组做权限叠加。数据库层面:
pre_common_membergroupid:主用户组(唯一)extgroupids:逗号分隔,存放多个附加用户组 ID,实现多组身份。
为什么必须增加多用户组(扩展用户组)
1. 业务身份出现交叉,单用户组无法表达
单用户组的痛点:用户身份互斥,只能二选一。
举例真实站长场景:- 某人是 A 板块版主(主组 = 版主),同时购买了全站 VIP;单用户组下:要么变成 VIP,丢失版主权限;要么保留版主,拿不到 VIP 权限。
- 某个老会员,同时又是活动嘉宾、特殊认证用户。
- 付费 VIP 到期,积分又很高,不能直接把他打回普通会员。
如果只用单用户组,只能:
- 不断新建复合用户组(版主‑VIP、版主‑嘉宾……),组合爆炸,后台配置爆炸;
- 改权限复制粘贴,极易出错。
多扩展用户组 = 权限乐高:主身份保留,叠加附加权限,不用创建大量复合组。
2. 区分「积分晋级主组」和「人工 / 付费授予的临时特殊组」
- 主用户组:跟随积分自动升降级(普通会员→中级→高级……),系统自动变更,由发帖、回帖积分驱动。
- 扩展用户组:人工授予、付费购买、带有效期(VIP、嘉宾、特殊认证),不受积分升降影响。
如果没有扩展组:用户积分涨上去,系统自动升级主用户组,就会把手工给的 VIP 身份冲掉,VIP 直接失效,大量投诉。
这是早期版本非常致命的 bug 类痛点。3. 商业化运营需求:付费会员、限时特权
2005‑2008 年大量论坛开始做收费 VIP:VIP 是附加特权,不是替换原有等级。
- 用户本来是高级会员,花钱买 VIP,只是多一堆权限,不应该把他的积分等级覆盖掉。
- VIP 到期,只需要移除这个扩展用户组,主用户组保持不变。
单用户组很难做时效会员:到期要记住恢复旧用户组,要额外存一份旧 groupid,业务逻辑写得很丑陋;扩展用户组原生支持过期清除,逻辑干净。
4. 版块权限精细化
Discuz 的版块权限,允许给用户组设置:看版、发帖、下载附件权限。
- 版块 A 只允许【版主】;版块 B 只允许【VIP】;
- 用户同时需要两个版块权限,单用户组做不到;扩展用户组叠加后,同时拥有两套权限集合。
5. 插件生态爆发,需要灵活的权限载体
大量第三方插件:勋章、活动、任务、认证、付费购买用户组,都需要给用户追加一套权限标签。
如果只能改主用户组,插件一运行就会破坏用户积分等级;扩展用户组作为附加槽位,插件只操作extgroupids,不碰groupid,插件之间互不打架。关键设计取舍:不是全部平等多组,而是「主组 + 若干扩展组」
Discuz 没有做成完全平等的多角色,保留唯一主用户组:
- 用户前台显示头衔、星星、图标,以主用户组为准,避免多个头衔混乱;
- 积分自动晋级逻辑只作用在主用户组;
- 权限合并:主用户组权限 ∪ 全部扩展用户组权限。
这是权衡:既解决权限叠加,又不破坏原有成熟的积分升级体系,老插件、老数据兼容。
反面:如果坚持只用单用户组怎么实现同样需求?
- 用户表增加字段保存备用用户组;VIP 到期手工回填;
- 大量创建组合用户组(版主‑VIP、高级‑VIP);
- 插件自己维护一套独立权限标记,脱离用户组体系。
缺点:逻辑分散、数据不一致、配置爆炸、第三方插件标准不统一。
一句话总结
早期论坛简单:每个人只有一个身份,用单用户组够用。
当论坛出现:版主同时是 VIP、付费限时特权、积分自动升级不能冲掉手工身份、跨版块复合权限,互斥的单用户组模型就扛不住,于是增加扩展用户组做权限叠加,保留主用户组维持积分等级与前台显示。一、模型定性:两套体系解决的是不同问题
Discuz! 本插件(levels) 用户组的本质 权限容器:主组+扩展组可叠加,几十项权限按组勾选 状态标记:六组固定、单一状态链互斥 等级 会员组按积分区间定级(组=等级) 等级是积分纯函数,与组解耦,从不落库 管理组 三级职务(版主/超版/管理员)+ 自定义组挂靠,20+ 项细分权限 全局 can_manage一刀切 + 管理员互操作开关这个差异是本插件的刻意设计(单一状态链杜绝“既禁言又停用”的叠加态;等级纯函数零迁移),不建议照搬 Discuz 的多组叠加模型。对比的意义在于吸收其运营细节,而非推翻结构。
二、本插件已优于 Discuz 的点(保持即可)
- 公开管理日志:Discuz 管理日志仅管理员可见,本插件可对全员公开、自然语言事件句;
- 处罚纪律:理由必填 + 预置理由归一 + 每次变更一行审计 + CSV 导出;Discuz 无强制理由;
- 状态恢复链:prev_flag 承接 + 互斥顶替,杜绝标志位残留;Discuz 禁言/禁访可叠加且各自恢复逻辑独立;
- 目标保护:uid=1 绝对保护、管理员互操作开关、渲染层/写路径双通道复审。
三、值得借鉴的改进点(按优先级)
P0 —— 体验闭环(改动小、价值高)
1. 处罚告知:让被处罚用户知道“为什么、到什么时候”(借鉴 Discuz 处罚必有告知)
- 现状差距:被禁言/禁访用户只看到「账号已停用中」或核心硬编码文案,不知道原因和到期时间——这是当前最大的体验缺口(管理员在成员列表能看到,本人看不到)。
- 建议:
levels_page_render替换回帖框文案时,从levels_members_of()(快照含e到期时间)取期限;理由可扩展镜像结构(members增存r字段,写路径levels_cache_rebuild顺带写入,红区仍零 DB);/levels 页为当前用户展示处罚状态行。改动量:小。
2. 处罚/解除的站内通知(借鉴 Discuz 警告通知作者)
- 现状差距:
levels_member_set/levels_member_remove只写审计,不通知本人;管理员可能忘了转达。 - 建议:复用
create_notification()(本插件升级通知已在用),设置页加开关(默认开),通知文案含组名、理由、期限。写路径一次插入,无红区问题。改动量:小。
3. 禁访支持期限(借鉴 Discuz「禁止访问」可设有效期)
- 现状差距:仅禁言有
expires_at,禁访(banned)只能永久,管理员被迫用“先禁访再手动解”。 - 建议:
expires_at推广到 banned;到期惰性清理扩展levels_expire_mutes()为通用到期清扫(现有 60 秒原子节流 + rowCount 认领模式直接复用);前台快捷操作禁访键弹窗出现时长档位。改动量:中,表结构无需变更(expires_at列已存在,语义放宽)。
P1 —— 权限精细化
4. 按等级差异化发帖频率(借鉴 Discuz 组级「每小时发帖数」)
- 核心已有现成钩子
security.rate_allow(放行判断,返回 false 触发限制)——本插件可挂接,按当前用户等级给出差异化阈值(如 LV0 每小时 2 帖、LV3 以上不限)。O(1) 内存判定,不碰红区。前置查证:实施前需核实该钩子 ctx(ip/bucket)与核心各 bucket 的触发点语义。改动量:中。
5. 个性签名分级(借鉴 Discuz「允许自定义头衔」组权限 → 改为等级门槛更贴合本插件哲学)
- 建议:签名增加「最低等级」设置(如 LV2 起可用),或上限随等级增长;纯内存判定,与门槛体系同构。改动量:小。
6. 前台快捷操作的权限口径对齐核心
- 现状差距:快捷操作用全局
can_manage();核心另有can_manage_topic($t)(主题操作已在用)。若核心未来引入版主/所辖版块概念,本插件应跟随核心函数而非自建分层——先查证两个函数的语义差异再决定,避免臆造。管理职务三级分层属于核心权限模型,插件不宜绕过核心自建,可经levels.query扩展 API 暴露状态供未来演进。
P2 —— 运营效率
7. 批量成员操作(借鉴 Discuz 批量编辑):成员列表多选批量移出 / 粘贴多用户名批量入组;逐用户事务写入(写路径允许),注意理由统一、失败逐条反馈。
8. 禁言时长档位后台可配:levels_mute_durations()现硬编码 7 档,可仿预置理由做成配置(归一化:正整数秒、去重、≤20 档)。
9. 处罚概况卡片:后台成员管理页顶部展示各组当前人数(从 JSON 镜像 O(1) 统计),一眼掌握全站处罚状态。长期可选(需慎重)
10. 组定义数据化(后台自定义组):Discuz 任意自定义组是它的核心卖点,但本插件六组固定键深度耦合状态链优先级、互斥集合、prev_flag 语义。若确有「VIP」「内测组」类需求,建议先评估:新增组是否只是「特殊组」的别名(用组名自定义即可部分满足——组名/颜色本就可配)。在需求明确前不动,避免为灵活性破坏单一状态链的确定性。
四、明确不建议做成 Discuz 机制
- 多组叠加(主组+扩展组):破坏单一状态链,收益低、复杂度高;
- 收费公众组/交易积分体系:bbs1 无交易积分基础设施;
- HTML 代码权限:Discuz 文档自己都警告安全隐患,与本插件「Markdown 渲染自带 XSS 防护」的边界冲突;
- 几十项权限 checkbox 大爆炸:与极简哲学冲突,本插件的「等级纯函数 + 六组状态」用 1/10 的配置面覆盖了小站实际需要的 80% 场景。
你迭代几轮,代码行数可以跟bbs1org看齐了😄
头疼,内核一更新,整个插件都需要更改,我还在用8.6.0版本。。。
兄弟,确实挺复杂的。我都看不动。😇
看起来很厉害的样子
这种等级没啥意思了,现在流行Discourse的信任等级。
很牛逼很牛逼