享耍插件开发文档
本页公开可读,覆盖插件包结构、manifest 字段、路由与后端分层、独立前端页、打包上传、安装生命周期、仓库签名接口、开发规范、SQL 防注入与提示词模板。按左侧目录可直接跳转。
01分发模型
仓库只负责「存放与分发」,业务站负责「安装与运行」。插件的主代码全部放在仓库的 zip 包里,业务站拉取后解包到自己的插件目录,前端页面随之部署到 /app/{slug}/。
- 一次上传:包上传到仓库后自动读取清单,记录版本、大小与 SHA-256。
- 多站安装:每个业务站用独立的接入账号签名请求,安装状态按会员单独保存。
- 逐步解耦:插件有自己的文件夹与表前缀,未来可以整包、整表迁移到独立库。
phub_ 前缀,业务站的插件表使用各自 manifest 中声明的 p_{slug}_ 前缀。02插件包结构
插件包解压后,根目录必须直接包含 manifest.json(允许外面多包一层目录,但只有一层)。
myapp-1.0.0.zip
├── manifest.json 清单(必须)
├── routes.php 该插件的路由表(可选)
├── migrations/
│ ├── 001.sql 建表脚本,按文件名顺序执行
│ └── 002.sql 后续增量
├── src/
│ ├── Controller.php 接口入口,命名空间 OOO\Plugins\Myapp
│ ├── Service.php 业务逻辑
│ └── Model.php 数据访问(只用自己的表前缀)
├── docs/
│ ├── README.md 插件详细简介(上传时写入仓库数据库)
│ └── PROMPT.md 本插件专属提示词
└── web/
└── index.html 独立前端页,安装后部署到 /app/myapp/
解包规则:src/ 会自动映射命名空间 OOO\Plugins\{Studly},web/ 会整体复制到业务站的 app/{slug}/。只提供前端页的轻量插件可以不写 src/ 与 migrations/。
| 文件 / 目录 | 是否必须 | 作用 |
|---|---|---|
manifest.json | 必须 | 插件身份、版本、表前缀、导航与权限声明。 |
routes.php | 可选 | 该插件专属 API 路由,未提供则只跑前端页。 |
migrations/*.sql | 可选 | 按文件名顺序执行,必须幂等。 |
src/ | 可选 | Controller / Service / Model,命名空间自动注册。 |
docs/README.md | 可选 | 插件详细简介。清单未写 intro 时,仓库上传会自动读取它并写入数据库。 |
docs/PROMPT.md | 可选 | 该插件的专属提示词,便于后续维护时直接复用。 |
web/ | 可选 | 独立前端页,部署到 /app/{slug}/。 |
03文档结构
仓库源码包内的文档与本站内容保持同一口径,修改规范时请同步更新,避免「页面一套、文件一套」。
plugin-hub/
├── README.md 部署、接入与账号说明
├── docs/
│ ├── ARCHITECTURE.md 仓库架构、数据表与签名接口
│ ├── PLUGIN-SPEC.md 插件开发规范(对应 02–12 节)
│ ├── PROMPTS.md 通用提示词模板(对应 14 节)
│ └── plugins/ 内置插件文档,每个插件一个子目录
│ ├── wangzhi/ README.md(简介)+ PROMPT.md(提示词)
│ ├── biji/
│ ├── wangpan/
│ └── caishui/
├── app/ MVC 控制器、视图与路由
├── lib/ 内核、SqlGuard、验证码
├── assets/ 后台、介绍页与文档样式
├── packages/ 插件 zip 存放目录(Web 禁止访问)
├── schema.sql 仓库表结构
└── index.php 唯一入口
- 在线文档:
/docs无需登录即可阅读;/admin/docs在后台内嵌同一份内容。 - 文档即规范:
PLUGIN-SPEC.md与在线文档冲突时,以在线文档为准,并回头修正文件。 - 提示词独立:
PROMPTS.md维护可直接复制的通用提示词;单个插件的提示词放在docs/plugins/{slug}/PROMPT.md。 - 一插件一目录:
docs/plugins/{slug}/README.md是插件简介的权威版本,打包时一并放进 zip 的docs/。 - 版本记录:规范发生破坏性变化时,在 README 顶部标注日期与不兼容项。
04manifest.json
清单决定插件能否被扫描到。缺少 name、version,或 slug、table_prefix 不合规的目录会被直接忽略。
| 字段 | 必填 | 说明 |
|---|---|---|
slug | 是 | 插件唯一标识,小写字母开头,仅 a-z0-9_,长度 2–32。必须与目录名、请求参数一致。 |
name | 是 | 展示名称,如「网址」。 |
version | 是 | 语义化版本,如 1.0.0。仓库以它作为包版本。 |
description | 否 | 一句话介绍,显示在插件中心卡片上。 |
intro | 否 | 插件详细简介(纯文本,可换行,建议 300–1500 字)。上传时写入仓库 phub_packages.intro,在「插件包」列表点「简介」可查看;留空则自动读取包内 docs/README.md。 |
icon | 否 | 图标标识,默认 grid,由前端图标库匹配。 |
table_prefix | 否 | 数据表前缀,^[a-z0-9_]{2,20}$,默认 p_{slug}_。 |
nav | 否 | { label, order, path };path 默认 /app/{slug}/。 |
permissions | 否 | 权限声明数组,如 ["notes.read","notes.write"]。 |
requires | 否 | 依赖的其它插件 slug 数组。 |
builtin | 否 | 是否随业务站内置,仓库分发的外部插件保持 false。 |
{
"slug": "myapp",
"name": "我的应用",
"version": "1.0.0",
"description": "一句话说清楚它解决什么问题。",
"intro": "详细简介:解决什么问题、主要功能、数据表与安全约定、安装后的使用步骤。",
"icon": "grid",
"table_prefix": "p_myapp_",
"nav": { "label": "我的应用", "order": 60, "path": "/app/myapp/" },
"permissions": ["myapp.read", "myapp.write"],
"requires": [],
"repository": { "type": "remote", "package": "myapp-1.0.0.zip" }
}
slug 的 table_prefix 一经发布不要修改,否则升级后会读不到旧数据。05路由表
routes.php 返回数组,每项为 [METHOD, 路径, '完整控制器类@方法', 中间件数组]。路径不带前导斜杠,内核会自动追加 plugin:{slug} 中间件,用于校验会员是否已安装并启用。
<?php
return array(
array('GET', 'myapp/items', 'OOO\\Plugins\\Myapp\\Controller@items', array('auth')),
array('POST', 'myapp/items', 'OOO\\Plugins\\Myapp\\Controller@create', array('auth')),
array('PUT', 'myapp/items/{id}', 'OOO\\Plugins\\Myapp\\Controller@update', array('auth')),
array('DELETE', 'myapp/items/{id}', 'OOO\\Plugins\\Myapp\\Controller@delete', array('auth')),
);
{id}会被编译成正整数占位,控制器方法按顺序接收(Request $request, $id)。- 请求地址为
/api/index.php?r=myapp/items,静态化后等价于/api/v1/myapp/items。 - 未安装:返回 403
plugin_not_installed;已安装但停用:403plugin_disabled。 - 路径与其它插件重名会被拒绝,统一使用
{slug}/...前缀。
routes.php,只提供前端页也能正常安装。06后端分层
Controller 继承 BaseController,方法签名固定为 (Request $request) 或 (Request $request, $id);统一用 $this->ok() 返回 JSON,用 $this->user($request) 取当前会员。
<?php
namespace OOO\Plugins\Myapp;
use OOO\Core\HttpException;
use OOO\Core\Request;
use OOO\Http\Controllers\BaseController;
class Controller extends BaseController
{
public function items(Request $request)
{
$user = $this->user($request);
$page = max(1, (int) $request->queryInt('page', 1));
$items = (new Model())->allByUser((int) $user['id'], 30, ($page - 1) * 30);
return $this->ok(array('items' => $items));
}
public function create(Request $request)
{
$user = $this->user($request);
$title = trim($request->inputString('title'));
if ($title === '') {
throw new HttpException('标题不能为空', 400);
}
$id = (new Model())->create((int) $user['id'], $title);
return $this->ok(array('id' => $id), 201);
}
}
Model 继承 BaseModel,只使用 manifest 里声明的表前缀,禁止跨插件读写:
<?php
namespace OOO\Plugins\Myapp;
use OOO\Models\BaseModel;
class Model extends BaseModel
{
const TABLE = 'p_myapp_items';
public function allByUser($userId, $limit = 30, $offset = 0)
{
$limit = max(1, min(200, (int) $limit));
$offset = max(0, (int) $offset);
return $this->fetchAll(
'SELECT * FROM ' . self::TABLE . ' WHERE user_id = ? ORDER BY id DESC LIMIT ' . $limit . ' OFFSET ' . $offset,
array((int) $userId)
);
}
}
LIMIT ? OFFSET ? 不能绑定参数;分页请先把值转成整数再拼接。分层约定
- Controller 只做参数校验、权限判断与响应组装,不写 SQL。
- Service 负责跨步骤业务逻辑,例如「校验归属 → 写入 → 记录事件」。
- Model 只访问自己的表,返回数组或标量,不抛业务文案。
- 错误统一抛
HttpException,由内核转换为{ok:false,message}。
07建表与表前缀
migrations/*.sql 按文件名顺序执行,单文件内以 ; 加换行分隔语句。执行异常会被忽略,所以语句必须写成幂等形式。
CREATE TABLE IF NOT EXISTS p_myapp_items (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id INT UNSIGNED NOT NULL,
title VARCHAR(120) NOT NULL DEFAULT '',
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY idx_user (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 所有表统一带
table_prefix,不要读写公共表。 - 时间字段用
DATETIME,写入当前时间后再按时间排序或查询。 - 增量变更新增
002.sql,不要回头改已发布的脚本。 - 卸载默认不删表,避免误删数据;确需清理请单独提供管理入口。
- 索引至少覆盖
user_id与常用排序字段,列表查询不要全表扫描。
08独立前端页
web/ 里的内容会在安装时复制到业务站的 app/{slug}/,形成 https://站点/app/{slug}/ 独立链路,不依赖主应用打包。
- 入口文件固定为
web/index.html,相对引用业务站公共脚本../js/config.js与../js/api.js。 - 登录态由
api.js统一从sessionStorage/localStorage读取并带上Authorization: Bearer,页面无需自己拼请求头。 - 窄屏要有一行两个图标的面包屑菜单,与主应用 H5 顶栏保持一致。
- 深链统一使用
?view=xxx或 hash,刷新后不丢状态。 - 空态、加载态、错误态必须齐全;错误提示用中文,不直接暴露堆栈。
<script src="../js/config.js"></script>
<script src="../js/api.js?v=1"></script>
<script>
window.OOOApi.request('myapp/items').then(function (res) {
render(res.items);
}).catch(function (err) {
toast(err.message);
});
</script>
09打包与上传
- 确认
manifest.json的slug、version与本次发布一致。 - 把插件目录内容(不含最外层目录)打成 zip,文件名建议
{slug}-{version}.zip。 - 不要把
node_modules、.git、测试脚本、临时文件打进包里。 - 到「插件包」页上传,仓库会自动读取清单并计算 SHA-256;清单里的
intro(或包内docs/README.md)会一并写入数据库,列表里的「简介」按钮可查看。 - 上传后用「下载」自测一次,确认解压后的目录就是
manifest.json所在层级。
version。09A更新包制作与发布
更新包就是版本号更高的完整插件包,不是差量补丁。业务站按版本号判断是否有更新,下载后整目录替换,因此更新包里必须包含完整的 manifest.json、后端代码、前端页面与迁移脚本。
版本号规则
| 类型 | 示例 | 适用场景 |
|---|---|---|
| 修订版 | 1.1.0 → 1.1.1 | 修 Bug、改文案、调整样式 |
| 功能版 | 1.1.1 → 1.2.0 | 新增页面、接口或配置项 |
| 不兼容版 | 1.2.0 → 2.0.0 | 数据结构或接口不向下兼容,需要迁移说明 |
制作步骤
- 复制当前插件目录,修改
manifest.json的version(slug、name保持不变)。 - 结构变更时在
migrations/下新增 SQL,文件按名称顺序执行,语句必须幂等(CREATE TABLE IF NOT EXISTS/ 先判空再ALTER)。 - 更新
docs/README.md或manifest.intro,写清本次更新内容(会同步到插件简介)。 - 本地跑一遍
php -l与核心流程自测,确认web/内资源自包含,不引用包外路径。 - 把插件目录内容(不含最外层目录)打成
{slug}-{version}.zip,不要带node_modules、.git、测试脚本。
上传位置
- 打开「插件包」页,列表已按插件归类折叠,展开目标插件。
- 在该插件分组内点「上传更新包」,选择 ZIP;分组表单会带上
slug,服务端校验 manifest 的slug必须一致,防止把 A 插件的包传到 B 插件下面。 - 上传成功后新版本出现在该插件版本列表顶部并标记「最新」;旧版本保留在同一分组内,可随时下架或删除。
- 首次发布的全新插件,仍使用页面顶部「上传新插件包」。
完整示例:shop 1.1.1 → 1.2.0
下面以「自营网店」新增订单备注为例,给出更新包需要改动的全部文件与打包命令,可直接照抄替换。
1. 目录变化
shop-1.2.0/ 打包时只打这一层内容,不要带 shop-1.2.0 外层目录
├── manifest.json version 1.1.1 → 1.2.0
├── routes.php 不变
├── migrations/
│ ├── 001_init.sql 历史文件,禁止修改
│ ├── 002_orders.sql 历史文件,禁止修改
│ └── 003_order_remarks.sql ← 本次新增
├── src/
│ ├── Controller.php 新增备注接口
│ ├── Service/RemarkService.php 本次新增
│ └── Model/OrderModel.php 增加备注读写
├── docs/
│ ├── README.md 追加 1.2.0 更新说明
│ └── PROMPT.md 不变
└── web/
└── index.html 订单详情页增加备注输入框
2. manifest.json
{
"slug": "shop",
"name": "自营网店",
"version": "1.2.0",
"description": "商品、订单、活动与对账一体化的自营网店。",
"intro": "1.2.0:新增订单备注,支持在订单详情编辑、列表检索;优化订单列表首屏加载。",
"icon": "bag",
"table_prefix": "p_shop_",
"nav": { "label": "网店", "order": 30, "path": "/app/shop/" },
"permissions": ["shop.read", "shop.write"],
"requires": []
}
slug 与 table_prefix 必须和上一版完全一致;只改 version、intro 以及本次确实变动的内容。3. migrations/003_order_remarks.sql
-- 新增表:可重复执行,重复安装不会报错
CREATE TABLE IF NOT EXISTS p_shop_order_remarks (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id INT UNSIGNED NOT NULL DEFAULT 0,
order_id INT UNSIGNED NOT NULL DEFAULT 0,
remark VARCHAR(200) NOT NULL DEFAULT '',
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY idx_user_order (user_id, order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
如果必须给已有表加列,MySQL 5.7 不支持 ADD COLUMN IF NOT EXISTS,按下面的存在性判断写法:
-- 给 p_shop_orders 加 remark 列(存在则跳过)
SET @exists := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'p_shop_orders'
AND COLUMN_NAME = 'remark'
);
SET @ddl := IF(@exists = 0,
'ALTER TABLE p_shop_orders ADD COLUMN remark VARCHAR(200) NOT NULL DEFAULT '''' AFTER status',
'DO 0');
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
migrations/*.sql 全部重跑一遍,且执行器会忽略 SQL 报错。所以每个迁移文件都必须幂等;改结构前先在测试库手工执行一遍确认生效。4. docs/README.md 追加更新说明
# 自营网店
## 1.2.0(2026-10-28)
- 新增:订单备注,支持在详情页编辑、列表按备注检索
- 优化:订单列表首屏加载速度
- 数据:新增表 p_shop_order_remarks(幂等建表,无需手工迁移)
- 注意:升级前系统会自动备份 p_shop_ 前缀下的全部表,
可在会员中心「数据备份」恢复;插件数据全站共享,恢复会影响所有会员。
5. 打包与本地校验
cd /path/to/shop-1.2.0
# 只打当前目录内容,排除依赖、版本库、测试与临时文件
zip -r ../shop-1.2.0.zip . \
-x "node_modules/*" ".git/*" "*.DS_Store" "*.log" "tests/*"
# 校验 1:根目录必须直接是 manifest.json,不能多套一层目录
unzip -l ../shop-1.2.0.zip | head -20
# 校验 2:记录 SHA-256,上传后与仓库显示值比对
sha256sum ../shop-1.2.0.zip # macOS: shasum -a 256 ../shop-1.2.0.zip
# 校验 3:PHP 语法
find . -name "*.php" -print0 | xargs -0 -n1 php -l
6. 上传与线上验证
- 后台「插件包」→ 展开
shop分组 → 「上传更新包」→ 选择shop-1.2.0.zip。 - 确认
1.2.0出现在该分组顶部并标记「最新」,1.1.1仍在列表中。 - 业务站会员中心等待索引刷新(≤5 分钟)→ 卡片显示「有更新 v1.2.0」。
- 点「更新到最新版」→ 确认弹窗 → 成功提示中会带升级前备份编号。
- 打开插件验证新功能;在「数据备份」中可见一条
update记录(表数、行数、版本区间)。 - 需要回退数据时,在备份列表对升级前那份点「恢复」;代码回滚由升级失败流程自动完成, 因此上线新版后先观察一段时间,再考虑删除旧版本。
curl 直接下载验证包可访问性,但签名接口需要接入账号,正常以「后台下载」或业务站安装结果为准。业务站更新行为
- 提示:业务站每 5 分钟刷新仓库索引,会员中心卡片在版本号后显示「有更新 vX」,并在操作区最前出现「更新到最新版」按钮。
- 安装:新安装一律拉取仓库最新版本。
- 自动备份:升级前按插件
table_prefix全量快照,存为api/data/plugin-backups/{slug}/*.json.gz,每个插件默认保留最近 5 份。 - 自动回滚:下载校验、解包或迁移失败时,自动恢复备份数据,并尝试重新拉取旧版本代码。
- 手动恢复:卡片「数据备份」入口可查看备份、立即备份、恢复或删除。
10安装与启停
- 浏览:业务站拉取仓库索引,把未下载的插件展示为「可安装」。
- 安装:下载 zip → SHA-256 校验 → 解包到临时目录 → 原子替换插件目录 → 复制
web/到/app/{slug}/→ 按顺序执行 migrations → 写入会员安装记录。安装时会自动选择仓库中的最新版本。 - 更新:会员中心检测到更高版本后,一键升级;升级前自动备份插件全部数据,失败自动回滚数据并尝试恢复旧版代码。
- 启用 / 停用:只改变该会员的启用状态,不删文件、不动数据。
- 卸载:仅删除会员安装记录,插件文件与数据表保留,方便再次安装。
安装状态表为 user_plugins(user_id + slug 唯一),全局包信息记录在 plugin_packages。
11仓库接口
业务站通过签名请求访问仓库,签名串为 account\ntimestamp\n{path?query},使用 HMAC-SHA256。
| 接口 | 方法 | 用途 |
|---|---|---|
/api/hub/index | GET | 插件索引:slug、名称、版本、描述。 |
/api/hub/package?slug= | GET | 下载 zip,响应头 X-Package-Sha256 供校验。 |
/api/hub/report | POST | 回报 download / install / update / uninstall 事件与站点。 |
X-Hub-Account: xiangshua-site
X-Hub-Timestamp: 1770000000
X-Hub-Signature: hash_hmac('sha256', account + "\n" + ts + "\n" + path, secret)
账号与密钥在「账号与密钥」页创建,可随时轮换;时间戳偏移过大会被拒绝。
账号类型与后台登录
仓库账号分为四类:管理员(全部权限)、发布者(上传与上下架插件包)、审计只读(仅查看数据)、接入账号(仅用于签名调用,不能登录后台)。三类可登录账号各自使用独立 Cookie 会话(phub_s_admin / phub_s_publisher / phub_s_viewer),同一浏览器可同时登录多个账号,打开多个窗口也互不影响;退出登录只清理当前角色的会话。
登录页强制图形验证码(4 位、5 分钟有效、一次性使用、最多尝试 6 次),由 /admin/captcha 签发,服务端校验通过后立即作废并写入 phub_sessions 记录。
12插件开发规范
规范分两部分:命名边界(必须遵守)与交付清单(提审前逐项确认)。违反命名边界会被仓库或业务站直接拒绝加载。
命名与边界
- slug:小写字母开头,
a-z0-9_,2–32 位,与目录名、包名一致。 - 命名空间:
OOO\Plugins\{Studly},如my_app→OOO\Plugins\MyApp。 - 表前缀:只使用自己的前缀,禁止读写
users、sessions等公共表。 - 包内路径:不得出现
..、绝对路径与盘符,仓库会直接拒绝。 - 接口前缀:路由必须以
{slug}/开头,避免与其它插件或内核路由冲突。 - 输出:前端渲染用户内容必须转义,富文本另行白名单过滤。
- 权限:接口默认要求登录;管理员能力不要暴露在普通入口。
交付清单
manifest.json字段完整,slug/version与包名一致。- migrations 全部幂等,重复执行不报错、不丢数据。
- 所有 SQL 走预处理绑定,分页参数转整数后再拼接。
- 每个接口都校验
user_id归属,不能越权读取他人数据。 - 前端页有加载态、空态、错误态;窄屏一行两个图标。
- 不使用 Composer,不依赖业务站未提供的扩展。
- 包内无
node_modules、.git、测试脚本与密钥文件。 - 上传后从仓库下载一次,本地解压核对目录层级与文件完整。
slug ^[a-z][a-z0-9_]{1,31}$ prefix ^[a-z0-9_]{2,20}$ zip 无目录穿越 SHA-256 校验
13SQL 防注入
仓库所有 SQL 必须经统一入口 hub_query() / hub_prepare() / hub_exec(),底层调用与业务站一致的 SqlGuard::assertSafe():先抹掉字符串字面量,再拒绝多语句(; 后跟可执行内容)与 --、/* */、# 注释截断;动态表名 / 列名必须通过白名单校验。参数仍由 PDO 预处理绑定,并关闭模拟预处理。
插件侧同样适用:Model 内不要拼接用户输入;需要动态排序字段时,用数组白名单映射,而不是直接拼 ORDER BY 参数。
// 正确:字段白名单 + 预处理
$allowed = array('created_at', 'title');
$order = in_array($sort, $allowed, true) ? $sort : 'created_at';
$stmt = $this->prepare(
'SELECT * FROM ' . self::TABLE . ' WHERE user_id = ? ORDER BY ' . $order . ' DESC LIMIT ' . (int) $limit,
array((int) $userId)
);
14提示词模板
下面五段是通用模板,可直接交给 AI。先替换花括号内容,再按需删减;一次只让 AI 做一件事,产出更稳定。更新包规则见 09A 更新包制作与发布。
docs/plugins/wangzhi/PROMPT.md、记事本 docs/plugins/biji/PROMPT.md、网盘 docs/plugins/wangpan/PROMPT.md、财税 docs/plugins/caishui/PROMPT.md。它们会随插件包放进 zip 的 docs/,二次开发时优先用专属提示词,通用模板只用于从零起新插件。A. 生成新插件骨架
你是享耍插件开发者。请按以下规范生成一个完整插件,只输出文件内容,不要解释。
插件信息:
- slug:{slug,小写字母开头,仅 a-z0-9_}
- 名称:{name}
- 版本:1.0.0
- 表前缀:{p_slug_}
- 一句话介绍:{description}
- 核心功能:{列出 3-5 条}
- 需要的数据字段:{列出字段与类型}
必须产出:
1. manifest.json:包含 slug、name、version、description、icon、table_prefix、nav、permissions、requires。
2. migrations/001.sql:CREATE TABLE IF NOT EXISTS {prefix}{table},utf8mb4,含 user_id 与 created_at。
3. src/Model.php:继承 OOO\Models\BaseModel,常量为自己的表名,SQL 全部预处理绑定。
4. src/Service.php:业务逻辑,校验入参与数据归属。
5. src/Controller.php:继承 OOO\Http\Controllers\BaseController,方法签名 (Request $request) 或 (Request $request, $id),统一用 $this->ok([...]) 返回。
6. routes.php:返回 [METHOD, 'slug/路径', 'OOO\Plugins\{Studly}\Controller@方法', ['auth']],路径不带前导斜杠。
7. web/index.html:独立前端页,引用 ../js/config.js 与 ../js/api.js,用 window.OOOApi.request('slug/路径') 调用;窄屏一行两个图标的面包屑;深色主题。
约束:
- 不读写公共表;不引入 Composer 依赖;不使用框架外全局函数。
- 列表分页先把页码与每页条数转整数再拼接 LIMIT。
- 所有用户输入输出转义;错误用 HttpException 并给出中文提示。
- 文件放在插件根目录,不要多包一层文件夹。
B. 改造现有插件页面
你是享耍插件前端开发者。只修改我指定的文件,不要重构无关代码。
插件:{slug}
目标页面:{web/index.html 或指定文件}
要改的内容:{逐条描述,例如「卡片一行 4 个」「标题最多 16 字」「移动端一行 2 个」}
保持不变:{列出不能动的结构、类名、接口}
要求:
- 沿用现有 CSS 变量与组件类名,不引入新框架。
- 交互必须可用键盘操作,按钮有 aria-label 或可见文字。
- 不破坏 ?view= 深链与刷新后的状态。
- 完成后说明改了哪些文件、每个文件的改动点与自测步骤。
C. 审查与修复
你是享耍插件代码审查员。请审查我提供的插件文件,按严重程度排序输出问题,不要直接大改。
检查项:
1. SQL 是否全部预处理绑定;分页是否整数拼接;动态字段是否白名单。
2. 是否读写公共表;表名是否使用自己的前缀。
3. 每个接口是否校验 user_id 归属;是否存在越权。
4. 输出是否转义;是否泄露密钥、路径或堆栈。
5. migrations 是否幂等;重复执行是否报错。
6. 前端加载/空/错误态是否齐全;窄屏是否一行两个图标。
输出格式:
- P0/P1/P2 分级列表,每条给出文件、行号、风险与最小修复方案。
- 最后列出需要我确认的问题,不要自行假设业务规则。
D. 制作并发布更新包
你是享耍插件仓库的发布者。我要为 {slug} 发布一个更新包。
当前版本:{x.y.z}
目标版本:{x.y.w}
改动内容:{新增 / 修复 / 结构变更}
是否改表:{是,涉及哪些表与字段 / 否}
请按以下规范产出:
1. 版本号:修订用 x.y.(z+1),向后兼容的新功能用 x.(y+1).0,不兼容用 (x+1).0.0;
同 slug + 同 version 重传只用于修复包文件。
2. manifest.json:slug、name、table_prefix 不变,version 递增,intro 或 docs/README.md 写明更新说明。
3. migrations:只新增文件(下一个序号.sql),不得修改历史迁移;语句必须幂等
(CREATE TABLE IF NOT EXISTS / 可重复执行的 ALTER),按文件名顺序执行。
4. 打包:完整包(src/、web/、migrations/、docs/),排除 node_modules、.git、测试脚本、密钥;
zip 内不套外层目录,命名 {slug}-{version}.zip。
5. 上传位置:后台「插件包」→ 展开 {slug} 分组 → 「上传更新包」,表单会带 slug,
服务端校验 manifest.slug 一致;上传后确认该版本置顶并标记「最新」,旧版本仍在。
6. 验收:业务站会员中心出现「有更新 v{version}」→ 一键更新成功;升级前自动备份
{table_prefix} 下全部表到 api/data/plugin-backups/{slug}/*.json.gz,失败可回滚。
输出:文件清单、manifest 与新增迁移内容、上传与验证步骤。不要改动 slug 与 table_prefix。
E. 站点侧升级与备份改造
你是享耍业务站后端 + 前端开发者。目标:会员中心支持「自动安装最新版 + 有更新提示 + 一键更新 + 数据备份恢复」。
后端(OOO\Plugin\PluginManager):
1. catalog() 增加 latest_version 与 has_update(本地 manifest 版本 vs 仓库索引,version_compare 比较);
内置插件与未安装项不提示更新。
2. install():本机缺包按仓库最新版本下载;本机版本落后时走升级流程。
3. update():校验已安装、非内置、仓库确有更高版本,再执行 runUpgrade()。
4. runUpgrade():先 backup() 全量快照插件 table_prefix 下的表 → 下载校验 SHA-256 → 解包 → migrations;
任一步失败 restoreBackup() 并尽力回拉旧版本,最后重新抛出原错误。
5. 备份:文件放 api/data/plugin-backups/{slug}/*.json.gz(JSON:tables → schema + rows),
元数据入 plugin_backups(slug/trigger_type/version_from/version_to/table_count/row_count/size/file),
保留最近 5 份自动剪枝;restore() 关闭外键检查后 DROP + 原建表语句 + 逐行回填。
6. 路由:POST plugins/{slug}/update、GET|POST plugins/{slug}/backups、POST plugins/{slug}/backups/{id}/restore、
DELETE plugins/{slug}/backups/{id},全部带 auth 中间件。
前端(会员中心 app.js / styles.css):
- 版本号后显示「有更新 vX」徽标;动作区顺序:更新到最新版 → 打开 → 启停 → 数据备份 → 删除。
- 备份弹窗展示类型/时间/版本/表数/行数/大小,支持立即备份、恢复、删除;
恢复前二次确认并提示「插件数据全站共享,会覆盖所有会员」。
输出:改动文件清单、接口返回示例、验证步骤(含更新失败回滚的验证方法)。