精华 干货 【开发必看】XIUNOX插件通知聚合中心接入指南 [复制链接]

管理员组

适用版本:XIUNOX 1.1.8(核心含 model/plugin_notify.func.php

一次接入,三通道送达:站内消息 + 邮件提醒 + 后台红点。开发者写一次代码,管理员在后台统一配置各通道开关与提醒邮箱。


1. 三通道能力总览与选型建议

通道

触发方式

管理员可配置

适用场景

站内消息

推送式:插件调 plugin_notify_fire() 时立即发

开/关

需要即时知晓的事件(新申请、新举报)

邮件提醒

推送式:与站内消息同一次调用发出

开/关 + 插件专属邮箱

管理员不常登录后台、事件重要需离线触达

后台红点

拉取式:插件提供 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 文件编写规范(三条禁区)

  1. 体内禁止 exit / die:hook 是被 eval 执行的代码片段,exit 会直接终止整个页面请求(白屏);

  2. 体内禁止 returnreturn 会从宿主函数提前返回,截断后续所有插件的收集;

  3. 注释禁止 // hook xxx 格式:会被框架 hook 编译器正则误匹配为 hook 占位符导致重复拼接崩溃,改用 // hook: xxx(冒号分隔)或纯文字注释。

同时在 conf.jsonhooks_rank 登记该文件,并递增插件 version

4. plugin_notify_fire() 参数表与最小示例

plugin_notify_fire($plugin, $event, $payload);

参数

类型

说明

$plugin

string

插件目录名,如 'xnx_verify'

$event

string

事件名(如 'new_pending'),用作节流键,只允许字母数字下划线连字符

$payload

array

见下表

$payload 支持的键:

类型

默认

说明

title

string

必填其一

标题(站内消息摘要、邮件主题)

content

string

必填其一

正文(纯文本)

url

string

''

跳转链接,相对路径自动转绝对

uid / uids

int / array

管理员

站内消息接收者,缺省=gid 1,2 全体管理员

email_to

string

配置链

收件邮箱覆盖(逗号分隔多个),解析顺序:payload > 插件配置 > 全局默认

channels

array

全部

限定通道,如 array('system','badge')

badge_flush

bool

true

事件后是否失效红点缓存

throttle

int

0

同 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.jsonversion 递增,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 设置在统一配置未设邮箱时仍作兜底,存量行为不破坏。

管理员标记为「干货」,奖励 +10 积分
最新回复
  • xiunox 一级用户组

    感谢分享,教程结构清晰,三通道解耦设计很合理。补充两点:测试红点 hook 时,可在其中临时写 error_log 观察执行情况,但务必遵守“禁 exit/return”规范;若邮件通道偶发失败,建议优先检查 SMTP 日志与节流策略。整体接入成本低,适合插件开发者遵循。【 AI 自动回复,仅供参考】

    1小时前

请先登录后再回复 登录