插件系统与开发指南
Clara 插件体系完整规范:目录结构、plugin.php 写法、生命周期、164 个钩子点全景、内置插件说明与开发指南。
06 · 插件系统
核心实现:[app/Core/Plugin.php](../app/Core/Plugin.php) + [app/Core/Hook.php](../app/Core/Hook.php)。插件目录:[content/plugins/](../content/plugins/)。
系统共预留 164 个钩子(61 个业务事件 fire + 103 个数据/视图插槽 filter;2026-10-01 源码普查校准),按四层体系组织:
| 层 | 类型 | 数量 | 用途 |
|---|---|---|---|
| 业务事件层 | `Hook::fire`(action) | 61 | 写操作完成后通知插件(发帖/回帖/交易/签到/后台管理……),只通知不改数据 |
| 数据管线层 | `Hook::filter` | 6 | 数据流经插件链式加工(正文渲染/页面标题/通知链接/头像 HTML/货币类型名/首页轮询指纹) |
| 前台视图插槽层 | `Hook::filter` | 82 | 在前台各页面位置注入 HTML |
| 后台视图插槽层 | `Hook::filter` | 15 | 在后台页面位置注入 HTML |
1. 插件规范
content/plugins/{目录名}/ # 目录名:字母数字开头,可含 - _,≤50 字符
├── plugin.php # 主文件(必需)
├── install.php # 启用时执行(建表/初始化,可选;需幂等——禁用后再启用会重复执行)
├── uninstall.php # 卸载前执行(清表,可选)
└── views/ # 插件视图(View::renderFile 渲染,可选)
plugin.php 结构:
<?php
/* 防直接 HTTP 访问(必须) */
if (!defined('BASE_PATH')) {
exit;
}
/*
Plugin Name: 插件名称
Plugin URI: https://example.com
Description: 插件描述
Version: 1.0.0
Author: 作者
*/
/* 在此注册钩子(文件被 include 时执行) */
Hook::on('thread_created', function ($data) {
// ...
});
/* 文件末尾 return 配置声明(后台「插件中心 → 配置」自动渲染表单) */
return array(
'config' => array(
'some_key' => array(
'label' => '配置项名',
'type' => 'text', // text / textarea / select 等
'default' => '默认值',
// 'options' => array(...) // select 用
),
),
// 'admin_url' => 'ai-reply', // 可选:声明后「配置」按钮直达该前台路由
);
要点:
- 元信息解析(`Plugin::parseMeta`):扫描全部块注释,取含 `Plugin Name` 的那个;行首 `*` 可有可无(兼容 WordPress 格式);键名小写下划线化。
- 防重复 include(`Plugin::loadFile` 的 `$loadedFiles` 请求级缓存):boot 与 configDef 共用一次 include,防止插件内全局函数重复声明致命错误——在插件里声明全局函数是安全的。
- 数据约定:自建表必须以 `plugin_{目录名}_` 为前缀;配置读写用 `Plugin::config($dir, $defaults)` / `Plugin::saveConfig($dir, $data)`(存 settings 表 `plugin_config_{目录}` 键)。
2. 插件生命周期
| 操作 | 入口 | 行为 |
|---|---|---|
| 启用 | 后台插件中心 → `Plugin::activate($dir)` | 执行 install.php → 写 `enabled_plugins` → 写 `installed_plugins`(此后显示卸载按钮)→ 初始化默认配置(已有不覆盖) |
| 禁用 | `Plugin::deactivate($dir)` | 仅移出启用列表;数据/配置保留,重新启用即恢复 |
| 卸载 | `Plugin::uninstall($dir)` | 先禁用 → 移除 installed 标记 → 执行 uninstall.php(清插件表)→ 删除配置键;插件文件目录保留(手动删除) |
| 每请求 | bootstrap → `Plugin::boot()` | include 所有已启用插件的 plugin.php(注册钩子);目录被删则跳过 |
安全边界:`Plugin::all()` 列表扫描只读源码不执行——未启用插件代码永不运行;boot/loadFile 异常全部捕获写 `storage/logs/plugin_error_Ymd.log`。
3. 钩子点全景(164 个 · 2026-10-01 源码普查校准)
3.0 钩子机制
Hook::on($name, $callback, $priority = 10); // 注册(Hook::register 为别名)
Hook::fire($name, $data); // action:依次执行回调,忽略返回值,返回原始 $data
Hook::filter($name, $value, $data = null); // filter:链式加工,返回最终值
Hook::has($name); Hook::clear($name); // 查询 / 清空(测试用)
- 优先级:第三参 `$priority` 越小越先执行(默认 10),同优先级按注册顺序。
- 异常隔离:所有回调在 try/catch 中执行,插件抛错只记 `storage/logs/plugin_error_Ymd.log`,不影响主流程。
- filter 回调签名:`function ($value, $data)`;核心未传上下文时回调只收到 `$value`($data 参数务必给默认值,如 `function ($html, $thread = null)`)。
- fire 回调签名:`function ($data)`,$data 为关联数组。
3.1 业务事件层(action,61 个)
系统与路由(2)
| 钩子 | 触发点 | 载荷($data) | 现有消费者 |
|---|---|---|---|
| `routes_register` | bootstrap.php,前台路由加载后 | `$router` | rank(注册 /rank 页)、api\_hub |
| `admin_routes_register` | admin\_routes.php,后台路由加载后、通配之前 | `$router` | ai\_reply、api\_hub、ai\_writer(各注册自管面板路由) |
账户与资料(9)
| 钩子 | 触发点 | 载荷($data) | 现有消费者 |
|---|---|---|---|
| `user_registered` | 注册成功 | `array(id, username, email)` | — |
| `user_login` | 登录成功(含记住我自动登录、2FA 二次验证) | `array(user, remember)` | — |
| `user_login_failed` | 登录失败(密码错误/账号锁定等) | `array(login, ip)` | — |
| `user_logout` | 退出登录 | `array(user)` | — |
| `profile_updated` | 用户中心保存资料 | `array(user_id, data)` | — |
| `avatar_updated` | 用户中心上传新头像 | `array(user_id, url)` | — |
| `password_changed` | 修改密码成功 | `array(user_id)` | — |
| `user_followed` | 关注用户 | `array(user_id, target_id)` | — |
| `user_unfollowed` | 取消关注 | `array(user_id, target_id)` | — |
主题与互动(14)
| 钩子 | 触发点 | 载荷($data) | 现有消费者 |
|---|---|---|---|
| `thread_created` | 发帖成功(前台发帖 / 后台发布 / ai\_writer 代发,三处均触发) | 前台:`array(id, user_id, forum_id, title)`;后台与 ai\_writer 额外携带 `thread_id`(同 id)、`content` | ai\_reply(主题级回复)、rank(活跃分) |
| `thread_updated` | 主题编辑保存后 | `array(thread 合并后数据, editor_id)` | — |
| `thread_deleted` | 主题删除:前台软删(仅状态实际变化时一次);后台删除带 `permanent=true` | `array(thread, operator_id[, permanent])` | — |
| `thread_moderated` | 版务操作(置顶/加精/关闭/隐藏)后 | `array(thread, field, column, value, operator_id)` | — |
| `thread_liked` | 点赞/取消点赞主题(两个状态共用) | `array(thread_id, user_id, liked)` | — |
| `thread_favorited` | 收藏/取消收藏主题 | `array(thread_id, user_id, favorited)` | — |
| `post_created` | 回帖成功(含 AI 回帖) | `array(id, thread_id, user_id, content)` | ai\_reply(楼层级回应)、rank |
| `post_deleted` | 回复删除后 | `array(post, operator_id)` | — |
| `post_liked` | 楼层点赞/取消 | `array(post_id, thread_id, user_id, liked)` | — |
| `poll_voted` | 投票提交成功 | `array(poll_id, thread_id, user_id, option_ids)` | — |
| `bounty_accepted` | 悬赏采纳最佳答案 | `array(thread, post, amount, currency_id, operator_id)` | — |
| `bounty_refunded` | 取消悬赏、托管款退回 | `array(thread, amount, currency_id, operator_id)` | — |
| `thread_purchased` | 购买付费主题成功 | `array(thread, buyer_id, price, currency_id)` | — |
| `thread_viewed` | 帖子被浏览(与 views 计数同语义:会话去重 1 小时后触发,插件无需自行防刷新;游客 user\_id=0;v2026.09.06) | `array(thread_id, user_id, forum_id)` | — |
附件(2)
| 钩子 | 触发点 | 载荷($data) | 现有消费者 |
|---|---|---|---|
| `attachment_uploaded` | 上传附件:`type=file` 文件模式与 `type=link` 网盘模式共用同一钩子名,插件按 `type` 分流 | `array(attachment_id, type, user_id, url, price)` | — |
| `attachment_bought` | 付费附件购买成功 | `array(attachment, buyer_id, price, currency_id)` | — |
财富与交易(5)
| 钩子 | 触发点 | 载荷($data) | 现有消费者 |
|---|---|---|---|
| `currency_changed` | 全站货币变动统一出口(currency\_change() 内部,覆盖签到/悬赏/附件/商城/卡密/邀请等全部记账点;事务提交后触发——监听器异常不影响已落账数据,也不可再回滚余额) | `array(user_id, currency_id, amount, type, note, balance)` | — |
| `gift_sent` | 送礼成功(帖内礼物与用户礼物共用) | `array(from_user_id, to_user_id, thread_id, gift, price)` | — |
| `member_purchased` | 购买会员成功 | `array(membership, user_id, price)` | — |
| `frame_purchased` | 购买头像框成功 | `array(frame, user_id, price, expire_at)` | — |
| `card_redeemed` | 充值卡兑换成功 | `array(card, user_id)` | — |
签到(2)
| 钩子 | 触发点 | 载荷($data) | 现有消费者 |
|---|---|---|---|
| `checkin_done` | 每日签到成功 | `array(user_id, streak, total)` | rank |
| `checkin_repaired` | 补签成功 | `array(user_id, date, cost, streak)` | — |
通知与私信(3)
| 钩子 | 触发点 | 载荷($data) | 现有消费者 |
|---|---|---|---|
| `notification_sent` | notify() 发送通知(合并进已有未读时 `merged=true`,新发为 `false`) | `array(uid, type, title, content, link, merged)` | — |
| `pm_sent` | 私信投递成功(新会话与会话内回复共用 deliver 统一出口) | `array(conv_id, from_id, to_id)` | — |
| `pm_deleted` | 私信会话删除(仅本人侧隐藏,对方不受影响;v2026.09.06) | `array(conv, user_id)` | — |
设计边界:通知中心的已读/删除(NotificationController::read/delete)不设事件——纯用户对本人通知的管理操作,无对外语义,插件监听无实际价值;如需通知联动,监听 `notification_sent` 即可。
后台管理(18)
| 钩子 | 触发点 | 载荷($data) | 现有消费者 |
|---|---|---|---|
| `user_edited` | 后台编辑用户保存 | `array(user_id, old, new, operator_id)` | — |
| `user_muted` | 后台禁言用户 | `array(user_id, until, reason, operator_id)` | — |
| `user_unmuted` | 后台解除禁言 | `array(user_id, operator_id)` | — |
| `user_banned` | 后台封禁账号 | `array(user_id, operator_id)` | — |
| `user_unbanned` | 后台解封账号 | `array(user_id, operator_id)` | — |
| `user_group_changed` | 后台变更用户组 | `array(user_id, old_group_id, group_id, operator_id)` | — |
| `forum_saved` | 后台保存版块(新建/编辑) | `array(forum_id, data, operator_id)` | — |
| `forum_deleted` | 后台删除版块 | `array(forum, operator_id)` | — |
| `group_saved` | 后台保存用户组 | `array(group_id, data, operator_id)` | — |
| `group_deleted` | 后台删除用户组 | `array(group, operator_id)` | — |
| `ip_ban_added` | 后台新增 IP 封禁 | `array(ip, reason, expires_at, operator_id)` | — |
| `ip_ban_updated` | 后台更新 IP 封禁配置 | `array(ip, reason, expires_at, operator_id)` | — |
| `ip_ban_removed` | 后台解封 IP | `array(ip, operator_id)` | — |
| `settings_saved` | 后台系统设置保存(含分组名与该组键列表) | `array(tab, keys, operator_id)` | — |
| `mail_batch_sent` | 后台邮件群发完成 | `array(subject, to, ok, fail, operator_id)` | — |
| `admin_threads_batch` | 后台帖子批量操作(删除/隐藏/恢复/移动等) | `array(act, ids, affected, operator_id)` | — |
| `report_submitted` | 用户提交举报(前台) | `array(type, target_id, reporter_id, reason)` | — |
| `report_handled` | 后台举报处理完成 | `array(report, operator_id)` | — |
插件生命周期(3)
| 钩子 | 触发点 | 载荷($data) | 现有消费者 |
|---|---|---|---|
| `plugin_activated` | 插件启用成功 | `array(dir, operator_id)` | — |
| `plugin_deactivated` | 插件禁用 | `array(dir, operator_id)` | — |
| `plugin_uninstalled` | 插件卸载完成 | `array(dir, operator_id)` | — |
版块关注与举报(3,v2026.09-10 补录)
| 钩子 | 触发点 | 载荷($data) | 现有消费者 |
|---|---|---|---|
| `forum_followed` | ForumController@followToggle 关注成功 | `array('user_id', 'forum_id')` | 关注通知类插件 |
| `forum_unfollowed` | ForumController@followToggle 取消关注 | `array('user_id', 'forum_id')` | 同 |
| `report_cleared` | AdminReportsController@clearPost 批量清空举报 | `array('status', 'count', 'operator_id')` | 举报清理联动插件 |
3.2 数据管线层(filter,6 个)
核心数据流的统一出口,回调 `function ($value, $data)`,必须 return 加工后的值。
| 钩子 | 位置 | $value | 上下文 $data | 典型用途 |
|---|---|---|---|---|
| `content_format` | helpers.php `md_render()` 返回处(所有帖文/签名/个人简介渲染的唯一出口) | 渲染后的 HTML | `array('raw' => 原始文本)` | 插件自定义标签(如 `[mytag]…[/mytag]`)、内容水印、外链转跳转页 |
| `page_title` | layout.php 页面标题 | 当前页面标题串 | 不传 | 按页面动态追加标题后缀 |
| `notification_link` | helpers.php `notify()` 入口 | 通知跳转链接 | `array(uid, type, title)` | 改写通知落地页(如积分任务通知跳插件页) |
| `avatar_html` | helpers.php `avatar_html()` | 头像 `<img>` HTML | `array(user, size)` | 头像挂件、VIP 边框 |
| `currency_type_name` | helpers.php `currency_type_name()` | 流水类型中文名 | 不传(返回值匹配) | 注册插件货币流水类型的中文显示名 |
| `home_sync_sig` | HomeController 首页轮询接口 `/home/sync` 指纹 | 轮询指纹串 | 不传 | 并入插件状态指纹,驱动已打开的首页自动刷新(仅首页有此钩子) |
/* 示例:自定义内容标签(在 md_render 之后、输出之前生效) */
Hook::on('content_format', function ($html, $data = null) {
return str_replace('[hr]', '<hr class="my-hr">', $html);
});
3.3 前台视图插槽层(filter,82 个)
回调接收并返回 HTML 串(视图用 `<?= Hook::filter(...) ?>` 直接输出)。输出前必须自行 e() 转义来源数据。
全站布局(layout.php,15 个)
| 钩子 | 位置 | 上下文 $data | 典型用途 |
|---|---|---|---|
| `layout_head` | `</head>` 前 | — | 注入 CSS/JS/meta(每页生效) |
| `layout_body_start` | `<body>` 顶部 | — | 全站浮层/顶部公告条 |
| `layout_body_end` | `</body>` 前(晚于核心脚本) | — | 底部 DOM/脚本 |
| `nav_links` | 顶部导航 | — | 加导航链接(用 `class="nav-link"` 保持样式) |
| `nav_logo_after` | 品牌 Logo/站名链接后 | — | 站点徽标/内测标注(只能放非交互内联元素,禁嵌套链接) |
| `nav_tools_before` | 顶栏工具区前(搜索/通知/头像之前) | — | 顶栏工具类入口 |
| `nav_search_after` | 顶栏搜索框后 | — | 搜索增强/快捷入口 |
| `user_menu_links` | 用户下拉菜单 | `array(user)` | 我的订单/我的商店等菜单项 |
| `main_area_before` / `main_area_after` | 主内容区顶/底 | — | 全站横幅;三栏主题左栏挂载点 |
| `footer_links` | 页脚 | — | 页脚链接 |
| `side_nav_links` / `side_nav_bottom` | 左侧吸附导航(后台开侧导航时) | — | 侧导航链接与底部卡片(`nav_links` 原样并入) |
| `lq_links` / `lq_bottom` | 左侧快捷导航卡(partials/left-quick-nav.php) | — | 快捷导航卡专用追加位(与顶部导航共用 `nav_menu_*` 开关) |
三栏主题接入模式:核心不提供固定左栏 DOM,由主题插件组合实现——`main_area_before` 输出 `<div class="plugin-left-col">` + `layout_head` 注入栅格 CSS(把 `.main-area` 改为 grid 三栏/两栏)+ `layout_body_end` 注入交互 JS。改核心布局零侵入,多主题插件可共存按启用互斥。
登录 / 注册(6 个)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `login_form_before` / `login_form_after` | 登录页顶/底 | — |
| `login_form_fields` | 登录表单内附加字段 | — |
| `register_form_before` / `register_form_after` | 注册页顶/底 | — |
| `register_form_fields` | 注册表单内附加字段 | — |
首页(home.php,5 个)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `home_content_before` | 首页主体顶部 | — |
| `home_hero_after` | 统计横幅之下、版块列表之上 | — |
| `home_left_top` / `home_left_bottom` | 三栏布局左栏顶/底 | — |
| `home_main_bottom` | 三栏布局中栏底 | — |
侧栏(首页/版块/帖子三处共用 + 语义化卡片锚点,12 个)
\| 钩子 | 上下文 $data |
\| --- |
\| `sidebar_top` / `sidebar_mid` / `sidebar_bottom`(mid = 首页热门主题后 / 版块页版块信息卡后 / 主题页作者卡后;home.php 不带上下文;forum.php 传 `$forum`;thread.php 传 `$thread`) |
\| 语义化卡片锚点(2026-09-10 补全,对齐 XiunoX「每张原生卡片后留缝」):首页 `sidebar_hot_after`(热门主题卡后)/ `sidebar_newuser_after`(新成员卡后)/ `sidebar_friendlink_after`(友链卡后);版块页 `sidebar_foruminfo_after`(版块信息卡后)/ `sidebar_mods_after`(版主卡后,无版主不渲染);帖子页 `sidebar_author_after`(作者卡后)/ `sidebar_related_after`(相关主题卡后,无相关帖不渲染)/ `sidebar_latest_after`(本版最新回复卡后)/ `sidebar_forummini_after`(版块迷你卡后)。ctx 与所在页侧栏钩子一致;卡片被条件隐藏时对应缝不渲染 |
版块页(forum.php,6 个)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `forum_content_before` / `forum_content_after` | 版块页顶/底(两种布局均生效) | `$forum` |
| `forum_threads_before` | 主题列表卡内顶部 | `array(forum, filter, cat_id)` |
| `forum_threads_after` | 主题列表卡内底部 | `array(forum, filter, threads)` |
| `forum_left_top` / `forum_left_bottom` | 三栏布局左栏顶/底 | — |
帖子详情页(thread.php,10 个)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `thread_head_after` | 标题/标签区与正文之间 | `$thread` |
| `thread_content_before` / `thread_content_after` | 正文前/后 | `$thread`(现有消费者:thread\_visitors) |
| `thread_actions_extra` | 主楼操作条内(点赞/收藏/分享按钮追加,与楼层 `post_actions_extra` 对称) | `$thread` |
| `post_author_extra` | 每层楼作者名附加徽章 | `$p` 楼层行 |
| `post_actions_extra` | 每层楼操作按钮区 | `$p` |
| `post_floor_extra` | 每层楼操作条下附加内容 | `$p` |
| `reply_form_before` | 回复表单前 | `$thread` |
| `thread_left_top` / `thread_left_bottom` | 三栏布局左栏顶/底 | — |
发帖 / 编辑页(thread-new\.php + thread-edit.php 共用,2 个)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `post_form_before` / `post_form_after` | 表单上/下方 | 发帖 `array(forum)`;编辑 `array(forum, thread)` |
编辑器(partials/editor.php,前后台共用,2 个)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `editor_toolbar_buttons` | 工具栏按钮区 | `array(canUpload, editorId)` |
| `editor_after` | 编辑器下方附加区 | `array(canUpload, editorId)` |
列表行(partials/thread-row\.php,1 个)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `thread_row_extra` | 主题行内附加信息(首页/版块/用户主页列表共用) | `$t` 主题行 |
用户主页(user.php,3 个)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `user_profile_header_after` | 主页头部之下 | `$u` |
| `user_profile_tabs` | tab 栏尾(链接用 `?tab=xxx#profile-tabs` 防跳页顶) | `$u` |
| `user_profile_tab_content` | tab 面板区首(插件自判 `$data['tab']` 渲染面板) | `array(tab, user)` |
用户中心(uc.php,3 个)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `uc_nav_links` | 中心导航尾 | `$me` |
| `uc_content_before` | 内容区顶部(每页生效) | `array(tab, me)` |
| `uc_tab_content` | 主区首(插件自判 `$data['tab']` 渲染整页) | `array(tab, me)` |
搜索 / 标签 / 签到 / 商城 / 礼物页(8 个)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `search_content_before` / `search_content_after` | 搜索页顶/底 | `array(q)` / `array(q, total)` |
| `tag_content_before` / `tag_content_after` | 标签页顶/底 | `array(tag)` |
| `checkin_content_before` | 签到页顶部 | `array(streak)` |
| `shop_content_before` / `shop_hero_after` | 商城页顶部 / 横幅下 | — |
| `gifts_content_before` | 礼物墙页顶部 | — |
私信页(pm-inbox.php + pm-view.php 共用,2 个;v2026.09.06)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `pm_content_before` / `pm_content_after` | 私信页顶/底(两页共用) | 收件箱不传;会话页传 `array(conv, partner)` |
插件自定义 tab 页模式(`uc_tab_content` / `user_profile_tab_content`):插件注册路由渲染页面骨架,或在钩子里输出面板——内置 tab 为 if/elseif 链且无 else 兜底,插件 tab(如 `shop`)不命中任何内置分支,仅插件内容渲染;tab 链接用 `?tab=xxx`(user 主页需带 `#profile-tabs` 锚点)。
应用中心(apps.php,2 个;v2026.09 补录)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `app_cards` | 应用中心功能卡片区(与 `nav_links` 同挂,结构化:icon/name/desc/url) | `$cards`(数组) |
| `apps_content_after` | 应用中心页底部 | `$html` |
专题页(topic.php / topics.php,5 个;v2026.09 补录)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `topic_content_before` / `topic_content_after` | 专题页顶/底(详情与总览共用,载荷含 null 兼容) | `array('topic' => $topic 或 null)` |
| `topic_main_bottom` | 专题详情主区底部 | `$html` |
| `topic_sidebar_top` / `topic_sidebar_bottom` | 专题详情侧栏顶/底 | `$html` |
3.4 后台视图插槽层(filter,15 个)
回调接收并返回 HTML 串,仅在后台页面生效;后台只加载 admin.css(样式需自带 `<style>` 内联或经 `admin_head` 注入,不能依赖 app.css)。
布局(admin/layout.php,6 个)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `admin_head` | `</head>` 前 | — |
| `admin_body_start` | `<body>` 起始 | — |
| `admin_body_end` | `</body>` 前(晚于核心脚本) | — |
| `admin_nav_links` | 侧栏菜单尾(自组 `.nav-group` 结构) | `array(module)` 当前模块 |
| `admin_content_before` / `admin_content_after` | 内容区顶/底(每页生效) | `array(module)` |
注意:核心侧栏菜单按多级管理组模块授权自动过滤(`Auth::adminModules()`),但插件经 `admin_nav_links` 输出的菜单项不会被自动过滤——插件需自行调用 `Auth::adminModules()` 判断(返回 null=全权限,否则为模块名数组)再决定是否输出。
各管理页(9 个)
| 钩子 | 位置 | 上下文 $data |
|---|---|---|
| `admin_dashboard_stats_after` | 仪表盘统计卡下方 | `array(stats)` |
| `admin_dashboard_content_after` | 仪表盘页尾 | `array(stats, env)` |
| `admin_settings_tabs` | 系统设置页 tab 栏尾 | `array(tab)` |
| `admin_settings_panels` | 系统设置页面板区(配套 tabs 加的页签) | `array(tab)` |
| `admin_user_edit_sections` | 用户编辑页底部区块 | `$u` |
| `admin_group_form_after` | 用户组编辑页表单下方 | `$group`(新建时 null) |
| `admin_thread_row_actions` | 帖子列表行操作区 | `$t` 行数据 |
| `admin_thread_form_after` | 帖子发布/编辑页表单下方 | `array(context: new/edit[, thread])` |
| `admin_tools_items_before` | 系统工具列表前置项(自组 `.tool-item` 表单) | — |
集成系统设置页模式(`admin_settings_tabs` + `admin_settings_panels` 配对使用):
/* 1. tab 栏尾加页签链接 */
Hook::on('admin_settings_tabs', function ($html, $data = null) {
return $html . '<a class="tab-btn ' . (($data['tab'] ?? '') === 'myplug' ? 'is-active' : '') . '"'
. ' href="' . aurl('settings?tab=myplug') . '">我的插件</a>';
});
/* 2. 对应 tab 渲染面板 */
Hook::on('admin_settings_panels', function ($html, $data = null) {
if (($data['tab'] ?? '') !== 'myplug') {
return $html; /* 非本插件 tab 直接返回 */
}
return $html . '<form method="post" action="' . url('myplug-save.html') . '">'
. csrf_field() . ' /* 表单字段 */ </form>';
});
关键约束:面板表单必须提交到插件自有路由(经 `routes_register` 注册)——核心 `settings/save` 端点有 `$keys[$tab]` 白名单,只接受内置 tab 的固定键名,插件的 POST 字段会被丢弃。自有路由内自行做 `Auth::isAdmin()` + `Csrf::check()` 后 `Plugin::saveConfig()`。
4. 内置插件(17 个)
4.1 ai\_reply — AI 智能回复(v1.4.1,答案风格对齐 GEO 问答工厂:结论前置 + 分段展开)
源码:[content/plugins/ai\_reply/plugin.php](../content/plugins/ai_reply/plugin.php)。OpenAI 兼容多提供商自动回帖。
- 触发机制(事件驱动):`thread_created`(新主题触发)+ `post_created`(用户回帖时挂其楼层下针对性回应);@机器人强制触发;判定规则:机器人身份排除、版块白名单、用户组、关键词、`reply_all`(默认开)/`reply_to_bot`、每日限额、每主题上限 `thread_max`、每对话上限 `conv_max`。
- 上下文感知:`ctx_thread_len` 帖子正文字符数(0=仅标题);`ctx_history` 沿 `reply_post_id` 回溯楼层链(0-10 层,含 AI 自身发言)——用户 B 回复 A 时 AI 可见 A→B 脉络。输入侧保留 `[em]url[/em]` 完整表情标签给 AI;输出侧修复 URL 前缀(补 `/` 或 `http` 开头为 `/assets/gif/xxx`)确保 `md_render()` 解析为表情图。
- 生成参数:`temperature` / `max_tokens` 注入请求 payload(max\_tokens=0 不传);`answer_max` 清理长度。
- 四策略轮询:顺序 / 随机 / 故障转移(failover)/ 并发竞速;提供商连续失败自动熔断、到期自动恢复。
- 回复延迟:`delay` 0-300 秒模拟人工(shutdown 后 sleep,锁持有期间防重复)。
- AI 标识:`ai_tag` 回复末尾 `<!--AI_TAG-->标识文本` 注释锚点,前台 `ai_tag_extract()` 提取渲染为动态徽章(紫色渐变 + 流光描边 + 图标呼吸)。
- 敏感词:内置约 240 词默认表(赌博色情/毒品暴恐/诈骗引流/辱骂/账号证件收购/荐股暴富等类别,避开日常用词防误伤);命中整条不发记 fail;用户可增删(上限 20000 字符)。
- 数据表:`plugin_ai_reply_providers`(提供商)、`plugin_ai_reply_logs`(调用日志:触发源/请求/回答/状态/耗时)。
- 后台面板:声明 `admin_url` → `admin.php/ai-reply` 五标签面板(基本/提供商/触发规则/敏感词/日志统计),日志面板支持 retry 一键重新生成(删原记录同步重跑 worker)与筛选计数(全部/成功/失败各状态数量)。
- 配置迁移:`reply_all_migrated` / `prompt_migrated` / `sw_migrated` 一次性 marker(旧默认值精确匹配才替换,用户自定义不动)。
4.2 site\_rank — 社区排行榜(v1.0.0)
源码:[content/plugins/site\_rank/plugin.php](../content/plugins/site_rank/plugin.php)。`routes_register` 注册 `/rank` 页面,`nav_links` 注入顶部导航「排行」入口(可配置隐藏)。
- 三榜 × 三周期:热门主题(点赞×3 + 回复×2 + 浏览÷10 加权)/ 活跃用户(周期内主题 + 回复合计)/ 财富榜(按配置货币余额);主题与用户榜支持近 7 天 / 近 30 天 / 累计周期切换。
- 权限口径:热门主题榜按当前访客可见版块过滤(`visible_forum_ids()`),缓存键含版块集合指纹(md5)——不同权限各自缓存互不污染,多数访客共享同一份。
- 缓存:`Cache::remember('sr_*', 600)` 十分钟缓存;核心「系统工具 → 清理缓存」可整体失效。每榜条数(10-100)与财富榜货币在插件配置调整(模式一声明)。
- 无自建表:纯查询侧聚合(threads/posts/user\_currency),无累计分表,卸载零残留。
4.3 thread\_visitors — 帖子访客(v1.0.1)
`thread_content_after` 挂载点输出最近浏览该帖的会员头像列表;仅记录登录用户浏览(`plugin_thread_visitors` 表),按时间倒序,定期清理;标题/显示数量/清理周期可配置。
4.4 ai\_writer — AI 智能写作(v1.5.5)
AI 标题池 + 文章生成 + 定时发布(参考 Typecho AIExcerpt 融合设计)。核心机制:
- 标题池(`plugin_ai_writer_titles`):AI 按主题批量生成标题(去重:池内 + threads 表 + 批内 + 敏感词)或手工批量导入;状态机 pending → preview/published/failed,preview 状态可在面板查看渲染后的暂存正文并一键发布/重新生成;筛选栏计数联动(状态 chips 计数跟随版块+分类筛选、版块/分类下拉各选项显示当前口径数量)。
- 标题缺陷过滤(`ai_writer_title_defects()`,生成期解析与体检共用的单一规则源):元话语(对排除清单的评论:「不要重复」「已覆盖」「已存在」「已有+引号」「给定列表」等)、提示词回声(AI 把指令文本原样当标题输出)、结构化残渣(行尾 `" Count.` 类英文残留)、纯括号片段(整条被一对括号包裹)、长引文复述、截断(逗号/顿号结尾)、列表前缀残留、冒号结尾、句中句号、过短/过长、无中文——每条规则按真实标题语料校准边界防误杀;缺陷行在计数前丢弃不消耗请求配额。
- 生成 prompt 构造:主题为主指令、排除清单只约束「不要重复或近似改写」(措辞刻意避免「含义角度完全不同」——同主题多次生成时会与「紧扣主题」自相矛盾导致跑题);清单尾部重申主题压制模型重尾漂移;自定义模板丢失 `{topic}` 占位符时守卫性补上(防纯静默跑题)。排除清单为该版块现有全部标题(最近 300 条,prompt 注入最近 100 条)。
- 标题池体检(`ai_writer_pool_checkup()`):pool 页「开始体检」同步只读扫描(范围跟随版块筛选),筛出两类问题供勾选清理(复用 titles-batch 端点)——①疑似无效(缺陷规则 + 复述池内标题精确检测);②重复/相似分组(SQL 精确查重盲区):规范化(去标点符号+小写)完全相同 / 仅数字差异(「10 个技巧」vs「12 个技巧」)/ 二 gram 倒排阻断 + similar\_text ≥ 80% 高度相似,DSU 并查集合并分组;无效与完全重复冗余项默认勾选,相似组人工判断。
- 异步生成任务(v1.5.2+,`plugin_ai_writer_jobs`):「AI 批量生成标题」「立即生成」「预览」均为异步入队 + `titles-progress` 端点轮询进度(2 秒),可离开页面后台继续,刷新自动恢复进度显示。可中断续跑看护模式(应对 FPM `request_terminate_timeout` 杀长循环):worker 全程持独立任务锁(`storage/cache/ai_writer_job.lock`,与调度锁分离)+ 单次预算 90 秒到点退出(任务保持 running),轮询/懒调度/cron 三处看护「running 且锁空闲」即重新拉起,排除清单等上下文从 DB 重建断点续跑。完成语义按实际入库数计(请求量 = 缺口 + 5 余量,入库硬上限 = 缺口);连续 3 块零新增判定主题穷尽收尾。
- 发布任务(`plugin_ai_writer_tasks`):每任务独立版块 + 子分类(v1.5:指定则只消费该分类标题)+ 机器人 UID(发帖数计入该账号、不暴露管理员身份)+ interval(≥300 秒)/daily(HH:MM)双频率 + 每次篇数(1-5)+ 任务级 model/温度/max\_tokens/提示词覆盖。
- 子分类支持(v1.5,schema v4):titles/tasks/jobs 三表补 category\_id(自愈迁移);生成/导入表单与任务表单均为版块→子分类联动下拉(catsMap JSON + data-aw-forum/data-aw-cat 通用绑定);标题池列表显示分类徽章、筛选栏加分类下拉(归属校验:cat 必须属于所选版块);一键清空尊重 cat 筛选;发布时 threads.category\_id 取自标题池行(列就绪才写)。
- 调度:三通道共用一把文件锁(`storage/cache/ai_writer.lock`)——① 前台懒调度(`register_shutdown_function` + 55 秒节流 + 先 `session_write_close` 释放会话锁 + `fastcgi_finish_request`,零配置);② 独立 `cron.php`(CLI / Webcron + cron\_key 校验);③ 后台手动操作走异步任务机制。单次调度受时间预算(max\_run\_sec)与任务数(max\_tasks)约束,防止刷版。
- AI 调用:直接读 ai\_reply 的 `plugin_ai_reply_providers` 表(无独立提供商配置),自带 4 策略调用层(order/random/failover 粘滞/race 并发竞速)与熔断记账(写回该表);超时取 max(本插件设置, 提供商 timeout) 防提供商面板短超时卡死长 prompt;全部失败时聚合各提供商具体错误(名称+原因)返回 job error 与日志;表不存在时优雅降级并提示。
- 发布副作用:完整复刻 ThreadController::store —— forums/users 计数、normalize\_tags、可选机器人经验(默认不发)、关注者通知(限 100)、`Hook::fire('thread_created')`;ai\_reply 启用且机器人 UID 不一致时面板预警防两个 AI 互怼。
- 内容管线:AI 末行输出 `<!--TAGS-->标签` 自动打标(与池内预设标签合并)、正文 H1 提取替换标题、代码围栏剥壳、max\_chars 截断、约 120 词内置敏感词表双重拦截(入池前 + 发布前);AI 输出清洗用多字节安全 trim(`ai_writer_trim`,preg\_replace /u 实现——PHP trim 的字符列表参数按字节处理,含中文标点会截坏多字节字符触发 SQL 1366),入库前 `mb_check_encoding` 防代理截断。
- 统计看板:`plugin_ai_writer_logs` 全量记录(action=title/article/publish、Token 出入、耗时、触发源 lazy/cron/manual/preview),看板含任务/发帖/Token 今日·近 7·30 天·累计卡片与 14 天成功失败趋势图;生成日志筛选 chips 带各状态计数。
4.5 api\_hub — API 工具箱(v1.0.2)
独立 API 聚合页面(移植自 Xiuno 版 chuan\_api\_hub 插件,全面重构并修复其缺陷)。前台 `/api-hub`(导航入口 `nav_links`),后台自定义面板 `admin.php/api-hub`(admin\_url 声明直达)。
- 23 个解析服务(`ah_services()`,按 group 分组:video 短视频 14 / image 图文集 3 / music 音乐 4 / world 海外 2):抖音、快手、小红书、微信视频号、微视、头条、微博、最右、皮皮虾、皮皮搞笑、B站、AcFun、虎牙、即梦、知乎、千问、豆包、汽水音乐、酷我、网易云(支持纯数字 ID)、QQ 音乐、TikTok、YouTube。每服务独立开关 + 独立 API URL 覆盖;上游统一 api.nycnm.cn + apikey。
- 规范化引擎(`ah_normalize`):上游 JSON 递归收集媒体 URL(key 名语义 + 扩展名双重判定,`ah_collect_urls`),统一输出作者/头像/标题/封面/视频数组/图片数组/互动数据/清晰度/时长;特殊平台适配(wxsph/zhihujx/qianwen/jimengai)。协议相对地址 `//xxx` 自动补全 https(原版直接丢弃)。
- 计费:多货币按次收费(`cost_cid` 下拉选择 + `cost_num`,0=免费),失败不扣费;余额预检 + `currency_change`(type=api\_hub)双保险;每日限额按用户按接口计成功次数(0=不限)。
- 防盗链处理(修复原版缺陷):前台所有 `img`/`video` 带 `referrerpolicy="no-referrer"`、`a` 带 `rel="noreferrer noopener"`——上游 CDN 的 Referer 白名单校验不再 403;视频提供内嵌播放器(多视频可切换源)、每个资源带「复制直链 / 新窗口」按钮、图集支持一键复制全部图片直链。
- 前台 UI(ah- 前缀 + 站点 CSS 变量):接口搜索 + 分组 chips 联合过滤、今日次数徽章实时刷新、余额实时刷新、最近解析(管理员看全站);游客可浏览、解析需登录(need\_login JSON 跳登录)。
- 后台面板:统计卡(累计/今日全站/启用数/开关)+ 基本设置 + 按分组的接口配置(批量启停/地址展开)+ 全站解析记录表(30 条)与按天数/全部清理(claraConfirm)。
- 数据表:`plugin_api_hub_log`(user\_id/service/input\_url/title/result\_url/cover\_url/cost\_cid/cost\_num/created\_at,InnoDB)。今日次数用单条 GROUP BY(原版逐接口 COUNT);表存在性请求级缓存(原版每请求 CREATE TABLE)。
4.6 member\_wall — 首页会员墙(v1.0.0)
源码:[content/plugins/member\_wall/plugin.php](../content/plugins/member_wall/plugin.php)。首页会员墙:推荐 / 最新 / 活跃 / 财富四榜展示,支持付费上墙(行锁 + 事务购买,官方并发安全范例);`Cron::register('member_wall/refresh')` 定时刷新榜单缓存;`home_sync_sig` 并入状态指纹驱动首页自动刷新。标准插件范本(skill 的 data-patterns 以它为并发/缓存/通知迁移示例)。
4.7 ad\_self — 广告位自助投放(v1.7.0)
源码:[content/plugins/ad\_self/plugin.php](../content/plugins/ad_self/plugin.php)。广告位自助购买:积分按天/周/月/年购买首页与内容区广告位,支持审核、续费、到期重新投放、草稿修改与到期自动下架。
- 坑位容量制:每插槽 N 个坑位(1-10),pending + active + 未到期均占坑,坑满不再出招租占位;购买走「提交前快速失败 + 事务内 settings 行锁权威复查」防并发超卖。
- 分型渲染:图片广告整幅堆叠在上、文字广告收 `auto-fill minmax(150px,1fr)` 栅格在下、空坑渲染招租占位、`shuffle` 零 JS 公平曝光;合规角标「广告」。
- 选位组合拳:正文流通栏位(home\_hero\_after / forum\_content\_before / thread\_content\_after)+ 侧栏三页位(按页分发)+ 通栏/全文模式复刻布局判定兜底——任何布局任何设备付费广告都可见。
- 数据表:`plugin_ad_slots`(广告位)、`plugin_ad_orders`(投放订单)。
4.8 lottery — 幸运抽奖(v1.3.0)
源码:[content/plugins/lottery/plugin.php](../content/plugins/lottery/plugin.php)。三合一抽奖:帖子回帖抽奖(预扣/三种开奖模式)+ 侧边栏概率抽奖 + 转盘抽奖(conic-gradient 扇区角宽 ∝ 真实概率,prefers-reduced-motion 三件套降级)。服务端权威判定 + 前端纯动画落格:结果永远在 POST 接口事务内产生(行锁/限额/扣费同核心口径),扇区签名 md5 防「渲染页 → 点击」窗口期改奖池落错格;奖品可独立指定货币,含中奖通知与后台管理。五张自建表(threads/participants/slots/draws/winners)。
4.9 ks\_exam — 入站考试(v1.0.0)
源码:[content/plugins/ks\_exam/plugin.php](../content/plugins/ks_exam/plugin.php)。新人入站考试:通过考试自动解锁互动权限并授予专属头衔,自带技术通识题库、随机组卷与防作弊机制。权限锁定插件的「入口体验层」范例:layout\_head 隐藏入口 + layout\_body\_end toast 守卫 + 页面插槽兜底三层(服务端权限位仍是最终防线;toast 类型是 `err` 不是 `error`)。六张自建表。
4.10 geo\_qa\_factory — GEO 问答工厂(v1.0.0)
源码:[content/plugins/geo\_qa\_factory/plugin.php](../content/plugins/geo_qa_factory/plugin.php)。AI 批量生产「悬赏提问 + 最佳答案」问答对:输入主题 → AI 生成草稿(`plugin_geo_qa_factory_queue`)→ 人工审核 → 一键发布并采纳(复刻悬赏帖语义)→ 自动进入 `/answers` 问答池与 llms-full.txt。`Cron::register('geo_qa_factory/gen')` 每日自动生成;后台经 `admin_routes_register` 注册 `/admin.php/geo-qf` 面板(admin\_nav\_links 注入导航)。与 ai\_reply 站点知识库共享事实源,两条 GEO 内容产线互补。
4.11 brand\_landing — 品牌落地页(v1.0.0)
源码:[content/plugins/brand\_landing/plugin.php](../content/plugins/brand_landing/plugin.php)。系统介绍落地页:实时数据展示本站功能特性与最新动态,滚动入场动画 + IntersectionObserver 数字计数(服务端直出真实值保 SEO,prefers-reduced-motion 全跳过)。插件独立全屏页范例:不套 layout.php 自写完整 `<!DOCTYPE html>`,主题跟随复刻核心判定链(访客 cookie > theme\_auto\_current() 时段自动 > 后台默认),CSS 变量引自 app.css。
4.12 quick\_reply — 快速回帖(v1.1.0)
源码:[content/plugins/quick\_reply/plugin.php](../content/plugins/quick_reply/plugin.php)。楼层一键快捷回复:免验证码用户点击即回帖;需验证码用户组点击短语填入回复框(输码后正常发表),防灌水与禁言/头像校验对齐核心回帖链路。
4.13 copy\_tail — 复制保护小尾巴(v1.0.0)
源码:[content/plugins/copy\_tail/plugin.php](../content/plugins/copy_tail/plugin.php)。复制帖子内容自动追加来源小尾巴(站点/标题/链接/时间)并弹窗提示,防止无署名搬运。
4.14 mourning — 黑白哀悼模式(v1.3.0)
源码:[content/plugins/mourning/plugin.php](../content/plugins/mourning/plugin.php)。国家哀悼日/重大事故悼念场合一键开启全站黑白模式:纯 CSS 滤镜实现前台灰度显示(后台保持彩色),支持自动生效日期(每年重复/一次性),过零点自动恢复,普通刷新即生效。
4.15 pet\_garden — 论坛萌宠
源码:[content/plugins/pet\_garden/plugin.php](../content/plugins/pet_garden/plugin.php)。萌宠养成互动:用户领养宠物、喂养升级,侧栏萌宠榜展示(全站排行),社区趣味玩法类插件。
4.16 sync\_platform — 外站同步
源码:[content/plugins/sync\_platform/plugin.php](../content/plugins/sync_platform/plugin.php)。帖子一键同步到外站平台:公众号(官方 API)+ CSDN(模拟登录),文章多平台分发,扩大内容触达面。
4.17 geo\_check — GEO 检测
源码:[content/plugins/geo\_check/plugin.php](../content/plugins/geo_check/plugin.php)。单篇帖子的 GEO 可视化体检:收录状态、爬虫访问、推送记录、结构化数据逐项检测,与核心「GEO 每日体检」互补(全站体检在核心,单篇诊断在此插件)。
5. 插件开发指南
5.1 最小可用插件
<?php
// content/plugins/hello/plugin.php
if (!defined('BASE_PATH')) {
exit;
}
/*
Plugin Name: Hello
Description: 在帖子正文后追加一句问候
Version: 1.0.0
Author: Me
*/
Hook::on('thread_content_after', function ($html, $thread = null) {
return $html . '<div class="hello-box">感谢阅读「' . e($thread['title'] ?? '') . '」</div>';
});
启用:后台「插件中心 → 启用」即生效(无需重启,每请求 boot 加载)。
5.2 带配置与自建表
- `install.php` 中用 `Db::q()` 建表(表名 `Db::t('plugin_hello_items')`,自动带前缀);`uninstall.php` 中 `DROP TABLE`。
- `plugin.php` 末尾 `return array('config' => array(...))` 声明配置 → 插件中心出现「配置」按钮(`AdminPluginsController::settings` 通用表单)。按钮仅启用后显示,配置端点校验启用状态(未启用插件的主文件不得被 include 执行)。
- 读配置:`Plugin::config('hello', array('k' => '默认值'))`。
5.3 自定义后台面板
return array(
'admin_url' => 'hello-admin', // 「配置」按钮直达 url('admin.php/hello-admin')
);
Hook::on('admin_routes_register', function ($router) {
$router->any('/admin.php/hello-admin', function () {
Auth::boot();
if (!Auth::isAdmin()) {
abort_page(403, '无权限');
}
if (Request::isPost()) {
Csrf::check();
// 保存配置 Plugin::saveConfig('hello', $_POST)
}
$content = View::renderFile(BASE_PATH . '/content/plugins/hello/views/admin.php', array(
'cfg' => Plugin::config('hello'),
));
echo View::render('admin/layout', array('pageTitle' => 'Hello 设置', 'content' => $content));
});
});
注意:插件后台路由必须自行做 `Auth::isAdmin()` + `Csrf::check()`(核心守卫不覆盖插件路由);且必须注册在 `admin_routes_register` 钩子内(先于通配路由匹配)。
5.4 核心公共设施:Cache 缓存与 Cron 计划任务(v2026.09.06)
插件需要的两类通用能力已抽入核心,不要再自造轮子(参照 member_wall 迁移:删掉自有的 `mw_cache_get/set/purge` 约 30 行,改 `Cache::remember` 一行式,行为不变)。
缓存([app/Core/Cache.php](../app/Core/Cache.php))——榜单、热点数据、外部接口响应等:
/* 读缓存,未命中查库并回写 5 分钟(最常用) */
$rows = Cache::remember('mw_rank_new_0', 300, function () {
return Db::all('SELECT ...');
});
/* 配置变更时主动失效(不等 TTL 自然过期) */
Cache::forget('mw_rank_new_0');
- 键约定:加插件前缀防冲突(如 `mw_`、`aw_`);只含 `[A-Za-z0-9_.-]`(其余字符自动转下划线)。
- `remember` 的空值也算命中(防空穿透:查询失败返回空数组也被缓存,不会每次打穿到库);需区分「未命中」用 `Cache::get($key) === null`。
- 后台「系统工具 → 清理缓存」会一并清掉(`*.cache` 后缀约定)。
计划任务([app/Core/Cron.php](../app/Core/Cron.php))——定时刷新、清理、汇总等:
/* plugin.php 顶层注册(boot 阶段即生效) */
Cron::register('member_wall/refresh', '会员墙榜单刷新', 300, function () {
foreach (array('new', 'active', 'rich') as $t) {
Cache::forget('mw_rank_' . $t . '_0');
}
return '已刷新 3 个榜单'; /* 返回摘要,后台「系统工具→计划任务」展示 */
});
- 任务名全局唯一,建议 `插件目录/功能`;间隔 ≥60 秒。
- 触发零配置:前台有访问即自动驱动(懒触发);低流量站可在服务器配置 cron 每分钟请求 `/cron.html?key=密钥` 精准触发(密钥在后台系统工具页)。
- 任务体要求幂等(锁超时或进程被杀后下次触发会重跑);异常自动记录为失败,不连坐其他任务。
- 依赖 `cron_runs` 表(数据库升级建);未升级时任务静默跳过,站点不受影响。
- 特例说明:ai_writer 因需要「用户自定义间隔 + 每日定点 + 批次断点续跑看护」等超出固定间隔模型的能力,保留自有调度不迁移;简单定时需求一律用核心 Cron。
5.5 界面文案 i18n(v2026.09.15 起)
核心 i18n 机制详见 [02-核心框架.md](02-核心框架.md) Lang 章节(源串即键:zh-cn 直通,未收录回退中文永不空文案)。插件接入零门槛——照常写中文,输出层包 `t()`:
/* PHP 侧(视图/控制器输出) */
echo t('已复制 ') . $n . t(' 条');
echo t('奖励 %s 金币', array($amount)); /* 占位符 sprintf 语法 */
/* JS 侧(fetch 回包后的提示等):查 window.CLARA_I18N,zh-cn 空表回退原文 */
claraToast(T('购买成功'));
两大禁忌(违者 fatal,生产事故沉淀):
- 编译期常量位置禁调 t()——`static $x = array(...t()...)` / `const` / 类属性默认值 / 参数默认值皆编译级 fatal。static 用惰性初始化(`= null` + `if (=== null)` 填充)
- plugin.php 顶级代码禁调 t()——加载期 `setting()` 未定义,t() → `Lang::boot()` 即 fatal。`Cron::register` 的 `$label` 传中文字面量(后台任务列表展示期自动 `t()` 翻译),其余顶级 t() 移入函数/闭包体内
补译(可选增强,不补也不破功能):新串在 `content/lang/en.php` + `zh-tw.php` 同键补条目(键 = 中文源串逐字符一致;占位符序列/换行数/首尾空格守恒);仅 JS 用的串另加入 `content/lang/js-keys.php` 清单(JS 翻译表按此清单裁剪注入,不整包内联)。
5.6 开发注意事项
- 插件 PHP 文件必须加 `BASE_PATH` 防护头,防直接 HTTP 访问。
- 页面级样式用内联 `<style>` 随输出渲染,类名统一加插件前缀(如 `tv-`),复用 `app.css` 的 CSS 变量(`--bd`/`--soft-2`/`--tx-2`/`--radius-s`/`--ac`)保证主题一致。后台插槽输出的内容不能依赖 app.css(后台只加载 admin.css)。
- 插件抛错会被 Hook 捕获记日志,但不要在回调里做致命假设(如未判空直接用 `$thread['x']`)。
- 钩子回调签名:filter 型为 `function ($value, $data)`(`$data` 可能不传,需默认值);fire 型为 `function ($data)`。
- 多 tab 共用一个 save 端点的插件面板:保存时按表单 hidden 标识分支构造 `$new`,只更新本页字段,否则未提交字段会经 `array_merge` 默认值洗掉另一 tab 的配置。
- 监听 `thread_created` 时以 `id` 键为主(前台/后台/ai\_writer 三处触发均已对齐;`thread_id`/`content` 仅后台与 ai\_writer 额外携带,勿作必需字段依赖)。
- 动作型钩子(点赞/收藏等)携带状态布尔(`liked`/`favorited`),监听方需区分正向与取消操作,避免重复记账。
- 建表与手写 SQL 避开 MySQL 保留字:列名撞保留字(如 `explain`、`order`、`group`、`key`)会直接 1064 语法错误导致 install.php 中断(真实案例:ks_exam 首版 `explain` 列)。`Db::insert/update` 等封装会自动反引号列名,但手写 SQL(建表 DDL、SELECT 列清单)必须自己加反引号(`` `explain` ``),或干脆换列名(如 `tip`)。
本文档随系统发布维护;如发现与当前版本不符,欢迎到社区反馈。







