dunea / flarum-member-promotion
A configurable, staged and auditable member promotion system for Flarum 1.8.
Package info
github.com/dunea/flarum-member-promotion
Type:flarum-extension
pkg:composer/dunea/flarum-member-promotion
Requires
- php: ^8.1 || ^8.2 || ^8.3 || ^8.4
- flarum/core: ^1.8.19
Requires (Dev)
- flarum/approval: ^1.8
- flarum/testing: ^1.8
- phpunit/phpunit: ^9.6
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
适用于 Flarum 1.8 的可配置、多阶段、可审计会员晋级扩展。包名 dunea/flarum-member-promotion,扩展 ID dunea-member-promotion。
扩展只管理用户组身份;发帖、上传等权限仍由 Flarum 权限表和 FoF Upload 管理。它不会自动创建业务用户组,也不会覆盖用户的完整组列表。
状态
- 后端、数据库迁移、管理 API、CLI、Mithril 后台页、中英文翻译和生产前端构建已完成。
- 已在
crazymax/flarum:1.8.19(PHP 8.4.21)和 MariaDB 10.11.19 上通过 39 个 PHPUnit 测试、112 个断言;前端生产构建与 TypeScript 类型检查也已通过。 - 已实际构建派生镜像,并确认 Composer 能从本地 path repository 安装扩展。
- 已提供 PHPUnit 单元/集成测试和 10 万用户、100 万帖子规模测试生成器。
- 已完成 1 万用户、10 万帖子、三阶段规则的 MariaDB 单 worker 快速压测;10 万用户、100 万帖子规模测试及真实多 worker 压测尚未执行,不应把快速压测结果线性外推到生产环境。
功能
- 任意多条规则;注册时长(小时/天)和有效回复数支持
>、>=,启用条件之间为 AND。 - 可选前置组、目标组、显式移除前置组;内置组 ID 1–4 不能作为规则组。
- 依赖拓扑排序,依赖优先;同层按优先级降序、规则 ID 升序,单批可连续晋级多阶段。
- 保存时检测直接/间接循环以及重复目标组造成的来源冲突。
- 自动晋级默认关闭。保存规则只创建可恢复后台任务,不在 HTTP 请求中扫描全站。
- 增量有效回复缓存、去重待办、注册时长到期唤醒、稳定 ID 游标、数量/时间双预算、数据库认领、三次重试。
- 插件授予来源、人工撤销阻止、用户暂停、排除组、dry-run、状态变化审计日志、运行心跳。
- 规则版本快照;结构性修改先预览确认,历史成员不会被迁移或降级。复制和删除规则均要求二次确认;删除只从管理列表移除并停用规则,审计所需的历史数据继续保留。
有效回复口径
第一版全站统一采用以下口径:
posts.type = comment且number > 1,排除主题首帖和系统事件;- 帖子和主题均未隐藏;
- 帖子和主题的
is_private = false; - 安装
flarum/approval且字段存在时,帖子和主题均须is_approved = true; - 作者必须存在。
创建、隐藏、恢复、删除和审核通过使用增量计数。主题隐藏、恢复及硬删除会通过持久化参与者索引创建分批失效任务,再按用户重建。未初始化或已失效的计数不会以 0 参与晋级。
is_private 是 Flarum 1.8 的扩展协作字段,本扩展保守排除私密内容。没有为特定私密讨论扩展建立显式集成;第三方若不使用核心的 is_private/隐藏字段,其内容语义不在保证范围内。
规则示例
先在 Flarum 后台创建“正式会员”“活跃会员”“资深会员”三个自定义组,再创建:
| 规则 | 前置组 | 注册时长 | 有效回复 | 目标组 | 移除前置组 |
|---|---|---|---|---|---|
| 正式会员 | 无 | > 3 天 |
> 10 |
正式会员 | 否 |
| 活跃会员 | 正式会员 | >= 30 天 |
>= 100 |
活跃会员 | 按需 |
| 资深会员 | 活跃会员 | >= 90 天 |
>= 500 |
资深会员 | 按需 |
人工授予的目标组被视为该阶段已完成,可参与下一阶段,但不会伪造为插件授予。移除前置组时,只有插件能确认唯一有效授予来源时才会移除;来源不明则保留并写日志。
后台任务
宿主机已有每分钟 schedule:run 时,无需 Redis、队列 worker 或额外 cron。验证:
sudo docker exec -u flarum -w /opt/flarum dididi-flarum-1 php flarum schedule:list sudo docker exec -u flarum -w /opt/flarum dididi-flarum-1 php flarum member-promotion:status
“尚未检测到运行”只表示扩展尚未写入心跳,不代表调度器一定未安装。
CLI
# 指定用户;--dry-run 不写组、授予历史、日志或任务状态 php flarum member-promotion:check --user=42 --dry-run php flarum member-promotion:check --user=42 --rule=3 # 全量或按规则扫描默认只排入后台;--foreground 只追加执行一个受预算限制的批次 php flarum member-promotion:check --all php flarum member-promotion:check --rule=3 --foreground # 计数重建 php flarum member-promotion:rebuild-counts php flarum member-promotion:rebuild-counts --user=42 # 状态、手动处理一批、确认修复根因后重置失败项 php flarum member-promotion:status php flarum member-promotion:run php flarum member-promotion:run --retry-failed
后台任务固定每批最多处理 100 个用户、单次最多运行 20 秒,后台和 CLI 均不提供覆盖参数,避免误配置导致数据库负载突增。
一致性与人工操作边界
- 所有时间使用 UTC;同一用户加组由用户行锁串行化,数据库唯一约束和应用幂等共同防重。
- 加目标组、插件授予来源和可确认的前置组移除在同一数据库事务内。
- API 中人工移除插件授予的组会触发 Flarum
GroupsChanged并建立阻止记录。 - 扩展后台的“撤销并阻止恢复”是可靠入口。直接 SQL 修改不会发出事件;后续评估发现“有效插件授予存在、组关系却缺失”时会保守阻止恢复,但无法准确识别所有外部脚本的意图。
- 停用或删除规则、停用扩展、删除回复或提高门槛都不会自动降级。删除规则不会清理历史授予和审计记录;目标组/前置组结构变化不迁移历史授予,更推荐先复制并检查新规则,再删除旧规则并启用新规则。
- 日志清理只删除运行日志,不删除规则版本、授予来源、用户暂停或人工阻止。
安装、更新与卸载
针对 crazymax/flarum:1.8.19 的完整说明见 docs/INSTALL.zh-CN.md。该镜像不会持久化 /opt/flarum/composer.json、锁文件或 vendor,生产环境应使用文档中的派生镜像方案,而不是只在运行中容器执行一次 Composer。
若要先在当前电脑手动体验后台和晋级流程,使用一键本地测试环境。它使用独立容器、网络和命名卷,不读取生产数据库或生产 /data。
FoF Upload
本扩展不调用 FoF Upload API。请在 Flarum 后台权限页把“上传文件”等权限授予晋级目标组;组成员变化后 Flarum 权限系统自然生效。扩展不会修改上传大小、MIME、存储驱动或上传记录。
测试
composer install composer test cd js npm ci npm run build npm run check-typings
测试覆盖条件边界/AND、依赖与间接循环、冲突、多阶段连续晋级、幂等、dry-run、有效回复口径和管理接口越权。更多集成与规模验证方法见 docs/TESTING.zh-CN.md。
目标基础镜像在 2026-10-10 的 composer audit 中存在其上游依赖告警(league/commonmark 两项、league/flysystem 一项);本扩展没有新增运行时第三方依赖。部署前应结合 Flarum 官方可用版本评估升级,详见安装文档。