适用版本:XIUNOX 1.1.8(核心含
model/plugin_notify.func.php)一次接入,三通道送达:站内消息 + 邮件提醒 + 后台红点。开发者写一次代码,管理员在后台统一配置各通道开关与提醒邮箱。
1. 三通道能力总览与选型建议
通道 | 触发方式 | 管理员可配置 | 适用场景 |
|---|---|---|---|
站内消息 | 推送式:插件调 | 开/关 | 需要即时知晓的事件(新申请、新举报) |
邮件提醒 | 推送式:与站内消息同一次调用发出 | 开/关 + 插件专属邮箱 | 管理员不常登录后台、事件重要需离线触达 |
后台红点 | 拉取式:插件提供 count hook,核心定期聚合查询 | 开/关 | 待办类事件的持续可见提醒(列表页角标、侧边栏徽章) |
选型建议:绝大多数插件只需要一个事件(如"新待办"),三个通道全开即可——plugin_notify_fire() 一次调用全部搞定。红点通道需要额外提供 count hook(见第 3 节),不提供则红点自动缺席,不影响另外两通道。
2. 三通道联动关系(重要)
站内消息与邮件是推送式:由 plugin_notify_fire() 在事件发生时主动发出。 红点是拉取式:核心通过你提供的 plugin_notice_count.php hook 实时查询待处理数量,走 60s 聚合缓存(core_plugin_notice),与 fire() 调用无数据耦合。
两机制独立工作:
调用
plugin_notify_fire()时默认顺带失效红点缓存(badge_flush参数),保证"事件发生→红点立即出现";红点数量永远以你 hook 里的实时查询为准,即使邮件被节流跳过,红点依然准确。
推荐做法:插件内封装一个 pendingCount() 方法(查自己的待处理表),红点 hook 与业务代码共用它,保证口径一致。
3. 红点 hook 协议(plugin_notice_count.php)
在插件目录新建 hook/plugin_notice_count.php:
<?php exit;
// 待审核 XX 计数(插件通知聚合红点数据源)
// 由核心 plugin_notice_count_all() 读取并隔离执行(错误计 0 不影响其他插件)
// 协议:写回 $data['count'](待处理数)与 $data['url'](后台待处理页地址)
// 规范:体内禁 exit/die/return;注释禁 hook 占位符格式
$_my_pending = db_count('your_table', array('status' => 0));
$data['count'] = intval($_my_pending);
$data['url'] = function_exists('admin_url') ? admin_url('plugin-setting-your_plugin-pending') : '';
核心约定:
文件头必须写
<?php exit;:核心读取文件后剥离该头部再隔离执行,防直接访问,是项目标准写法;$data['count']为 0 或未写时该插件不显示红点;$data['url']是红点/角标点击后的跳转地址,必须用admin_url()(从前台/任意上下文生成带admin/前缀的后台 URL),并加function_exists守卫;执行环境在函数作用域内:只能使用全局函数与
$data变量,不要引用外部局部变量;单插件 hook 报错被核心 try/catch 隔离(该插件计 0),不会影响其他插件。
hook 文件编写规范(三条禁区)
体内禁止
exit/die:hook 是被eval执行的代码片段,exit会直接终止整个页面请求(白屏);体内禁止
return:return会从宿主函数提前返回,截断后续所有插件的收集;注释禁止
// hook xxx格式:会被框架 hook 编译器正则误匹配为 hook 占位符导致重复拼接崩溃,改用// hook: xxx(冒号分隔)或纯文字注释。
同时在 conf.json 的 hooks_rank 登记该文件,并递增插件 version。
4. plugin_notify_fire() 参数表与最小示例
plugin_notify_fire($plugin, $event, $payload);
参数 | 类型 | 说明 |
|---|---|---|
| string | 插件目录名,如 |
| string | 事件名(如 |
| array | 见下表 |
$payload 支持的键:
键 | 类型 | 默认 | 说明 |
|---|---|---|---|
| string | 必填其一 | 标题(站内消息摘要、邮件主题) |
| string | 必填其一 | 正文(纯文本) |
| string |
| 跳转链接,相对路径自动转绝对 |
| int / array | 管理员 | 站内消息接收者,缺省=gid 1,2 全体管理员 |
| string | 配置链 | 收件邮箱覆盖(逗号分隔多个),解析顺序:payload > 插件配置 > 全局默认 |
| array | 全部 | 限定通道,如 |
| bool |
| 事件后是否失效红点缓存 |
| int |
| 同 plugin+event 节流秒数(高频事件建议 300-1800) |
返回值:array('system'=>..., 'email'=>..., 'badge'=>...),各通道值为 'off'(关闭)/ 'sent:N' / 'skipped' / 'throttled' / 'error:xxx' / 'flushed'。
最小示例
// 用户提交新申请后通知管理员(三通道,5 分钟节流)
try {
if (function_exists('plugin_notify_fire')) {
plugin_notify_fire('your_plugin', 'new_pending', array(
'title' => '有新的 XX 申请待审核',
'content' => '用户 ' . $username . ' 提交了新申请,请前往后台处理。',
'url' => function_exists('admin_url') ? admin_url('plugin-setting-your_plugin-pending') : '',
'throttle' => 300,
));
}
} catch (\Throwable $e) {
error_log('[your_plugin] notify exception: ' . $e->getMessage());
}
务必 try/catch 包裹:通知是副作用,异常不能影响业务主流程。function_exists 守卫用于兼容旧核心(无此函数时静默跳过,或回退 AdminNotifyService::audit())。
审核处理后刷新红点
管理员处理完待办(通过/拒绝/删除)后,主动失效红点缓存,让角标实时消失:
if (function_exists('plugin_notice_flush')) {
plugin_notice_flush();
}
// 待办清零时同时清节流键,让下次新待办立即再推送(键名规则:core_plugin_notify_throttle_{plugin}_{event})
if (function_exists('cache_delete')) {
cache_delete('core_plugin_notify_throttle_your_plugin_new_pending');
}
5. 管理员设置页说明
后台 → 插件 → 通知设置(/admin/?plugin-notice.htm):
全局默认提醒邮箱:所有插件未单独设置邮箱时的邮件收件地址;
每插件一行:待处理数 + 三通道开关(站内消息/邮件/红点)+ 插件专属提醒邮箱;
测试邮件:预填当前管理员邮箱,验证 SMTP 通道;
未配置时三通道默认全开,邮箱回退 AdminNotifyService 兜底链(插件自有
admin_notify_emails设置 > 管理员账号邮箱)。
插件自身设置页无需再提供任何通知邮箱/开关配置——统一收敛到通知设置页,避免两处配置打架。
6. 插件 count 查询的缓存建议
红点聚合本身有 60s 缓存(core_plugin_notice),你的 hook 执行频率已被限制:
简单 COUNT 查询(走索引,<10ms):不必再加缓存,直接查;
重查询(多表 JOIN / 大表扫描):在插件侧加短 TTL 缓存(建议 30s),注意两层缓存叠加后红点最大延迟 = 60 + 30 = 90s,属可接受范围;
// 重查询示例:插件侧 30s 短缓存
$_my_pending = CacheHelper::remember('pending_count', 30, function() {
return heavy_pending_query();
}, 'your_plugin');
7. 真实接入范例:xnx_verify
hook/plugin_notice_count.php(红点数据源):
<?php exit;
// 待审核认证申请计数(插件通知聚合红点数据源)
// 由核心 plugin_notice_count_all() 读取并隔离执行(错误计 0 不影响其他插件)
// 协议:写回 $data['count'](待处理数)与 $data['url'](后台待处理页地址)
// 规范:体内禁 exit/die/return;注释禁 hook 占位符格式
$_verify_notice_pending = db_count('xnx_verify_apply', array('status' => 0));
$data['count'] = intval($_verify_notice_pending);
$data['url'] = function_exists('admin_url') ? admin_url('plugin-setting-xnx_verify-pending') : '';
提交申请处(VerifyService::submitApply,节流 300s):
try {
if (function_exists('plugin_notify_fire')) {
plugin_notify_fire('xnx_verify', 'new_pending', array(
'title' => lang('xnx_verify_notify_admin_subject'),
'content' => lang('xnx_verify_notify_admin_body', array('username' => $_apply_username)),
'url' => $_verify_admin_url,
'throttle' => 300,
));
} else {
// 兼容旧核心:回退 AdminNotifyService::audit
...
}
} catch (\Throwable $e) {
error_log('[xnx_verify] submitApply notify exception: ...');
}
审核通过/拒绝处(approveApply / rejectApply,flush + 清节流键):
if (function_exists('plugin_notice_flush')) {
plugin_notice_flush();
}
$_pending_after = db_count('xnx_verify_apply', array('status' => 0));
if ($_pending_after == 0) {
if (function_exists('cache_delete')) {
cache_delete('core_plugin_notify_throttle_xnx_verify_new_pending');
}
}
conf.json:version 递增,hooks_rank 登记 "plugin_notice_count.php": 10。
8. 注意事项
错误隔离:单个插件的 fire() 异常或 hook 报错均被核心捕获(记
plugin_error日志),不影响业务与其他插件;但业务代码仍应自行 try/catch,保证主流程不受通知拖累;性能:聚合缓存单键(
core_plugin_notice,TTL 60s,后台缓存 TTL 配置页可调),每次后台页面加载最多一次 cache_get + 重建,红点成本与接入插件数量无关;卸载自愈:插件禁用/卸载后 hook 不再被收集,TTL 过期红点自然消失;核心卸载流程会调
plugin_notify_config_delete()清理通知配置,插件无需在 uninstall.php 里额外处理(自定义 setting 键仍按原有规范自行清理);邮件同步延迟:邮件由 SMTP 同步发送,高频事件务必设
throttle(建议 ≥300s),否则批量操作(如脚本灌入申请)会刷爆邮箱;红点 URL 必须指向后台:用
admin_url()而非url()(后者从前台上下文调用时不带admin/前缀,会跳到前台 404);旧插件兼容:插件自身 setting 中已有的
admin_notify_enabled=0会被继续尊重(推送通道整体关闭);已有admin_notify_emails设置在统一配置未设邮箱时仍作兜底,存量行为不破坏。



