本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:8月13日备份/sosite-plugin-dev/SKILL.md。
SKILL: SoSite 插件开发助手
本 Skill/Rule 包用于指导 SoSite(自研 MVC 社区系统,PHP 8.2 / MySQL 5.7-8.0,零 Composer 依赖) 的插件开发。
挂载到 AI 编程工具后,当任务涉及"写一个 SoSite 插件 / 加 hook / 加后台设置页 / 插件路由 / 插件视图"时,自动参考本包规范,避免写出白屏、数据错乱、不合规范或踩中已知坑的代码。
一、触发条件(命中即加载本 Skill)
- 用户要求编写/修改 SoSite 插件(前台功能插件、后台管理插件、钩子插件)
- 用户要求新增/修改 hook(如 init_after、route_register、nav_plugin_links 等)
- 用户要求新增插件后台设置页 / 后台路由 / 侧边栏入口
- 用户要求新增插件路由(前台页)或插件视图/静态资源
- 涉及
plugins/目录、plugin.json、Plugin\xxx命名空间、Plugin::hook的改动
二、硬规则(违反即返工)
R1 目录与命名
- 插件必须位于
plugins/{dir}/,{dir}用小写下划线(如daily_checkin、friend_links) - 命名空间 =
Plugin\{Dir}(目录名转 StudlyCase,如Plugin\DailyCheckin),Autoloader已支持Plugin\前缀映射 - 视图必须放
plugins/{dir}/views/(A+ 后resolveTemplate插件目录优先,控制器view('plugins/{dir}/xxx')自动解析到此处;不得再往app/Views/plugins/放模板) - 静态资源放
plugins/{dir}/assets/,页面引用直接写/plugins/{dir}/assets/xxx(经$this->url()),不要用asset()——asset()会解析到assets/plugins/发布目录,造成双份漂移(已知坑)
R2 plugin.json
- 必须包含:
name(中文名)、version(如1.0.0)、author、description、hooks(hook名 => 相对路径)、activated activated默认false(安装后默认停用,需后台手动启用,防未审查代码立即执行)hooks路径以hook/开头,如"init_after": "hook/init_after.php"
R3 生命周期
- 可选
plugins/{dir}/Plugin.php:namespace Plugin\{Dir}; class Plugin { static activate(): bool; static deactivate(): bool; static uninstall(): void; } activate()返回false会回滚启用;uninstall()内自行清理建表/数据(框架只负责删注册与缓存)
R4 控制器与路由
- 前台控制器
extends \app\Core\Controller;后台控制器 必须extends \app\Controllers\Admin\BaseController(自带 isAdmin + 二次密码验证) - 路由注册写在 hook 文件:前台
hook/route_register.php用$router->get/post('/path', ['\Plugin\Xxx\XxxController', 'method']);后台hook/admin_route_register.php同样注册(URL 不带 /admin 前缀,由后台容器鉴权) - hook 文件内
if (!isset($router)) return;防御性开头
R5 视图与安全
- 输出一律
$this->e(...)转义(XSS);POST 表单必须带csrf并Csrf::verifyOrDie($_POST['csrf'] ?? '') - 模板编译器
{if}内禁止函数调用(仅放行isset/empty,safeExpr 黑名单,白屏即查此) - 数据库 SQL:PDO native prepare 两条铁律(全局
ATTR_EMULATE_PREPARES=false):
- 同一命名占位符不能复用(
:now,:now会 HY093)——需要两次就拆成:created_at/:last_active_at - SHOW 语句不能有占位符(1064)——
SHOW TABLES LIKE :x必须$conn->quote()内联
R6 其他
- 改动
plugin.json后必须Plugin::refresh()重建缓存(plugins/plugins_cache.php),否则后台/前台读旧配置 - 导航/下拉入口:
nav_plugin_links钩子中$view = $GLOBALS['__view'] ?? null;后echo '<a href="...">' . $view->icon('xxx', 16) . '名称</a>' - 插件内建表不要放
initDatabaseSchema();在Plugin::activate()/uninstall()里建/删(或懒建)
三、工作流(标准开发顺序)
- 需求拆解:功能是前台页面 / 后台管理 / 纯钩子?需要哪些 hook?是否建表?
- 搭骨架:创建
plugins/{dir}/+plugin.json(activated:false)+Plugin.php(如需要)+hook/+views/+assets/ - 写路由:
hook/route_register.php(前台)或hook/admin_route_register.php(后台),require 控制器 - 写控制器:继承正确基类;方法内取数据 →
$this->view('plugins/{dir}/xxx', [...]) - 写视图:
plugins/{dir}/views/xxx.php,用$this->section('title'/'content')+$this->e()转义;资源用$this->url('/plugins/{dir}/assets/...') - 写 hook 逻辑:按需在
hook/*.php中实现(导航、注入 CSS/JS、数据联动) - 验证:
php -l全部新文件 → 后台启用插件 → 页面实测(白屏查 safeExpr/PDO,样式丢失查资源路径) - 清理:确认不破坏其他插件;卸载测试
uninstall()能清数据
四、速查手册索引(references/)
| 文件 | 内容 |
|---|---|
references/01-plugin-structure.md | 插件目录结构、plugin.json 字段、生命周期、最小示例(抄即用) |
references/02-hooks-catalog.md | 全部 hook 名称、触发点、可用变量与参数 |
references/03-api-cheatsheet.md | 控制器/模型/工具类 API 速查(Database、Settings、Points、AuditLog、Csrf、Upload、Search、Plugin) |
references/04-security.md | 安全规范与已知坑(XSS/CSRF/路径穿越/PDO 铁律/白名单) |
references/05-ui-templates.md | 模板语法、图标、资源引用、样式约定(含双份 CSS 坑) |
references/06-troubleshooting.md | 排错清单(白屏/404/样式丢失/计数错乱等根因速查) |
references/07-plugin-patterns.md | 典型插件模式与实现模板:场景→母版插件速查表、13 类模式代码骨架(后台 CRUD/审核流/签到积分/游戏/纯 hook/提交审核/注册拦截/收藏关注/业务子系统/并发防重/纯工具/设置页注入) |
plugin-system.md | 插件系统介绍:架构、加载流程、生命周期、核心 21 个 hook 总表(含触发文件/参数)、14 插件注册分布、hook 使用统计、已知坑 |
五、给 AI 的协作约定
- 开发新插件先查
references/07-plugin-patterns.md场景速查表,找到最接近的现有插件作母版,在其结构上改(勿从零写) - 动手前先
php -l目标文件;改动插件后按 R6 提示用户Plugin::refresh() - 遇到"模板改了没生效":先确认文件在
plugins/{dir}/views/(不是 app/Views/plugins)、资源不是asset() - 涉及新表:DDL 与
activate()/uninstall()成对出现,幂等(CREATE TABLE IF NOT EXISTS) - 涉及数据库查询:先判断是否有 SHOW / 复用占位符场景,再写 SQL
- 涉及后台页:继承 Admin\BaseController,别忘了 CSRF 与
__nav_active高亮