本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:8月13日备份/SoSite-三功能落地方案-在线升级-插件仓库-定时任务.md。
SoSite 三功能落地方案:在线升级 · 插件仓库 · 定时任务
文档日期:2026-08-07
参考对象:
bbs/(bbs1.org 极简论坛,单文件 PHP,GitHub 开源)目标程序:SoSite V5.0.0(自研 MVC,PHP 8.2 / MySQL 5.7-8.0,零 Composer 依赖)
说明:本方案为设计文档,非最终实现代码;落地前需按实际代码结构复核函数签名与路由注册方式。
⚠️ 2026-08-07 决策:定时任务经评估对现有程序价值低(现有"周期性"维护需求均为访问触发 + 节流模式,如 config.php 会话 GC、邀请码/红包过期按访问判断),暂不开发。确有需要时按 §4 以插件形式实施。
一、背景与目标
用户希望借鉴 bbs1.org(下称 bbs)的三项能力:
| 能力 | bbs 的实现 | SoSite 现状 |
|---|---|---|
| 在线升级 | 后台一键检测 GitHub 版本、逐文件差异下载、原子替换、失败回滚、自动同步数据库结构 | 手动下载 update 包 → 覆盖 → 删孤儿文件 → 清缓存(全手动,易漏) |
| 插件仓库 | 远程市场 JSON 源、一键安装/更新/卸载、sha256 校验、安装后默认停用 | 插件仅本地手动放置 + 后台启停;无市场、无远程安装 |
| 定时任务 | ?a=cron 统一调度、租约防并发、失败暂停重试、任务日志、插件 manifest 声明任务 | 无任何定时任务机制(仅 AuditLog::purgeOldLogs 注释"可 cron 调用") |
目标:在不改动 SoSite 现有 14 个插件结构与核心架构的前提下,新增三项能力,全部走"后台管理员操作 + 强校验 + 备份回滚"的安全路径。
二、bbs 参考实现机制拆解
2.1 在线升级(Setup.php)
核心思路:以"逐文件内容哈希比对"代替"整包下载"。
- 版本检测:调用 GitHub API
repos/{repo}/commits/{branch}获取最新 commit sha,再递归拉取文件树,得到每个文件的 git blob sha(sha1('blob ' . 长度 . "\0" . 内容))。 - 本地比对:对本地每个文件算同样的 blob sha,与远端清单对比 → 得出
新增 / 更新 / 删除三类变更清单(us_local_changes())。 - 升级执行(
setup_update_run()→us_install_files()):
- 二次校验远端 sha 未漂移(防 TOCTOU);
- 逐个下载
raw.githubusercontent.com/{repo}/{sha}/{path},用远端清单里的 blob sha 校验下载内容; - 先把原文件全部备份到临时目录(含哈希核对);
- 原子替换:
copy(临时→swap) + rename(swap→目标),替换后再次校验; - 全部成功后才写入版本记录
update-state.json(sha / updated_at / files); - 任一步失败:按逆序回滚所有已替换文件,并回滚版本记录。
- 并发防护:
us_acquire_lock()用flock(LOCK_EX|LOCK_NB)占升级锁,重复触发直接拒绝。 - 数据库结构同步:文件更新完成后不立即执行建表(新代码尚未生效),而是"延迟到下一个请求"(
us_defer_schema_after_update()→ 下次访问时us_run_deferred_schema()执行 schema 差异同步)。 - 依赖强制:勾选升级
index.php时,强制同时带上app/optional/Cron.php与Plugin.php(防止核心与可选模块版本错配)。 - 更新通知:后台检测到新版本后写入
update-state.json,通过站内通知提醒管理员升级。 - 其他:升级前后刷新 opcache(
us_refresh_opcache_after_update());升级页提示"勾选文件的本地修改将被覆盖"。
可借鉴点:内容哈希逐文件比对(精准、增量、省流量)、备份-替换-回滚三阶段、flock 并发锁、延迟 schema 同步、升级通知。
2.2 插件仓库(Plugin.php)
核心思路:单文件插件模型 + 远程 JSON 市场 + 强校验安装。
- 插件模型:每个插件 =
app/plugins/{id}/plugin.php单个 PHP 文件,文件内return [manifest](id/name/version/description/author/hooks/routes/admin_tabs/assets/cron/install/uninstall)。注册表app_plugins表存 manifest 快照,普通请求不扫描目录。 - 市场端点:
PLUGIN_MARKET_ENDPOINT?action=plugin_market_feed返回 JSON 列表,每条含元数据 +code(完整插件源码,need_code=true时才下发)+sha256。本地有 5 分钟缓存,失败回退到过期缓存。 - 安装流程(
plugin_market_install()):
- 校验:
<?php开头、sha256(源码)与市场一致、源码内嵌'id' => xxx与市场 ID 一致; - 备份现有插件文件 →
tmp + rename原子写入 →opcache_invalidate; - 注册表行置为停用(安装后必须手动启用,防止未审查代码立即执行);
- 禁用该插件的全部 cron 任务;保存市场 sha256/topic_id 元数据;标记资源重建。
- 注册表同步(
plugin_registry_sync()):扫描插件目录 →opcache_invalidate+include读 manifest → 白名单校验(hook/route/callback 函数名、cron interval 范围)→ upsert 注册表 → 删除已消失插件的注册与 cron 任务 → 重建合并资源plugins.css/js。 - 版本对比:
version_compare(远端, 本地) > 0→ 市场页显示"可更新"。 - 市场页面:后台"插件"tab → 市场视图(搜索、安装/更新/重新安装、展示 sha256 摘要)。
可借鉴点:远程 JSON 市场 + 本地缓存、sha256 强校验、安装后默认停用、注册表与磁盘同步、资源自动合并。
2.3 定时任务(Cron.php)
核心思路:数据库任务表 + 租约(lease)领取防并发 + 失败暂停重试。
- 入口:
index.php?a=cron(外部服务如 cron-job.org 每分钟 GET 触发),或 CLIphp index.php cron(自动把 argv 映射为a=cron)。路由执行set_time_limit(0)+ignore_user_abort(true)。 - 任务表
app_cron_tasks:plugin_id / task_name / callback / interval_seconds / enabled / status / attempts / failure_count / retry_limit / pause_seconds / pause_until / available_at / last_started_at / last_success_at / last_error / lease_token / lease_until。 - 租约领取(
cron_task_claim()):在事务内SELECT ... WHERE enabled=1 AND available_at<=now ORDER BY available_at LIMIT 1,随即UPDATE抢占(写 lease_token/lease_until,rowCount=1才成功)——多个并发 cron 请求同时进来也不会重复执行同一任务;已领任务被再次 claim 视为"租约超时",回收并记录失败。最多重试 3 次抢占。 - 执行(
cron_run()):循环领取到期任务 → 校验插件启用/回调存在 → 调用callback($plugin, $definition)→ 写任务日志app_cron_logs(status/message/时间)→cron_task_finish()释放租约并计算下次available_at。 - 失败策略:失败计数累加,达到
retry_limit后pause_until = now + pause_seconds(默认暂停 30 分钟);暂停到期后清零失败计数恢复调度——失败任务不会永久阻塞队列。 - 任务注册(
plugin_cron_sync()):插件注册表同步时,把 manifest 中cron声明(name → callback/interval)同步进任务表;interval 支持固定秒数(60~31536000)或"返回秒数的函数名"(后台可配间隔)。 - 后台页:插件 tab → 定时任务日志视图(
admin_plugins_cron_logs_page_html())。
可借鉴点:事务内租约抢占(比锁文件更稳、可跨进程)、available_at 时间片调度、失败暂停-恢复、任务日志表、插件 manifest 声明式注册。
三、SoSite 现状与差距
3.1 SoSite 现有架构(与方案相关部分)
| 模块 | 现状 |
|---|---|
| 路由 | app/Core/Router.php:get/post/any/group + dispatch(),支持 {param};入口在 index.php,路由注册集中在某处(需按实际代码确认) |
| 后台 | app/Controllers/Admin/*:BaseController(Auth::isAdmin() 拦截)+ Dashboard / Database(备份/导入/恢复)/ Blog / Category 等控制器 |
| 插件 | app/Helpers/Plugin.php:loadPlugins() 扫描 plugins/*/plugin.json + 写 plugins_cache.php 缓存;hook() 按 hook 名调用插件注册的 hook 文件;activate/deactivate/refresh/uninstall 已有 |
| 插件结构 | 多文件:plugins/{id}/plugin.json(声明 hooks → hook/*.php 文件)+ Plugin.php + views/ + assets/ + hook/;现有 14 个插件 |
| 数据库迁移 | protected/database/init.php → initDatabaseSchema()(幂等:CREATE IF NOT EXISTS + try/catch ALTER),install.php 已复用 |
| 配置 | config.php 常量(SOSITE_VERSION);站点设置存 settings 表(app/Helpers/Settings.php) |
| 升级现状 | update/ 目录 = 手动整改包(README 说明:备份 → 覆盖 → 删 9 个孤儿文件 → 清模板缓存 → 回归验证);main.zip 为源码包 |
| 审计 | app/Helpers/AuditLog.php(purgeOldLogs() 已注释"可 cron 调用") |
3.2 差距清单
- 无定时任务:无任务表、无调度入口、无后台管理页;签到/红包等插件的"每日重置"类逻辑目前靠访问触发或缓存标记(
.daily_checkin_table_cache等)。 - 无在线升级:只有手动 update 包;无版本检测、无文件差异比对、无回滚。
- 无插件市场:插件只能本地放置;无远程安装/更新。
- 插件为多文件结构:市场分发不能像 bbs 那样单文件下发源码,需 zip 分发包(zip 解压安全性是新增关注点)。
- 无 CLI 入口:定时任务可纯 HTTP 触发(
/cron路由),CLI 为可选增强。
四、落地方案一:定时任务(⚠ 暂缓,按需实施;建议插件化)
独立性强、无外部依赖、收益最高,且 bbs 的租约设计可完整迁移。
4.1 新增表(MySQL)
CREATE TABLE IF NOT EXISTS app_cron_tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
plugin_id VARCHAR(50) NOT NULL DEFAULT 'core', -- 核心任务 'core',插件任务用插件 ID
task_name VARCHAR(64) NOT NULL,
callback VARCHAR(100) NOT NULL, -- 函数名(白名单校验)
interval_seconds INT NOT NULL DEFAULT 86400,
enabled TINYINT(1) NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'idle', -- idle/running/paused/error
attempts INT NOT NULL DEFAULT 0,
failure_count INT NOT NULL DEFAULT 0,
retry_limit INT NOT NULL DEFAULT 3,
pause_seconds INT NOT NULL DEFAULT 1800, -- 失败暂停时长(秒)
pause_until INT NOT NULL DEFAULT 0, -- 暂停截止(unix 时间戳)
available_at INT NOT NULL DEFAULT 0, -- 下次可执行时间
last_started_at INT NOT NULL DEFAULT 0,
last_success_at INT NOT NULL DEFAULT 0,
last_error VARCHAR(500) NOT NULL DEFAULT '',
lease_token VARCHAR(32) NOT NULL DEFAULT '',
lease_until INT NOT NULL DEFAULT 0, -- 租约截止,超时回收
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_task (plugin_id, task_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS app_cron_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
plugin_id VARCHAR(50) NOT NULL DEFAULT 'core',
task_name VARCHAR(64) NOT NULL,
status VARCHAR(20) NOT NULL, -- success/failed/timeout
message VARCHAR(500) NOT NULL DEFAULT '',
started_at INT NOT NULL DEFAULT 0,
finished_at INT NOT NULL DEFAULT 0,
INDEX idx_task (plugin_id, task_name),
INDEX idx_finished (finished_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;表结构需加入
initDatabaseSchema()的幂等建表流程(新增一个createCronTables()步骤即可)。
4.2 新增核心文件
app/Helpers/Cron.php -- 调度执行器(租约领取/释放/超时回收/失败暂停/日志)
app/Controllers/CronController.php -- /cron 入口(校验 cron_key)
app/Views/admin/cron.php -- 后台任务列表页
app/Views/admin/cron_logs.php -- 后台任务日志页
app/Controllers/Admin/CronController.php -- 后台管理(启用/停用/立即执行/清日志)4.3 调度入口
// index.php 或路由注册处(按现有 Router 方式注册)
$router->get('/cron', [CronController::class, 'run']);
// CronController::run()
// 1) 校验 cron_key:settings 表存随机密钥,URL /cron?key=xxxx
// (bbs 未做 key,但被恶意高频触发会浪费资源,SoSite 建议加;
// 密钥在安装时生成,管理员可在后台重置)
// 2) set_time_limit(0); ignore_user_abort(true);
// 3) Cron::run() 循环领取并执行到期任务
// 4) 输出纯文本摘要(due/success/failed)部署方式(README 说明):
- 方式 A:cron-job.org / 宝塔计划任务 / Windows 计划任务,每分钟
curl -s "https://域名/cron?key=xxx" - 方式 B(可选增强):新增
cli.php入口,php cli.php cron直连数据库执行(不依赖 Web)
4.4 任务注册与执行
- 核心任务:
plugin_id='core',安装/升级时注册(首批接入:AuditLog::purgeOldLogs()日志清理、签到插件"昨日数据归档"、red_packet 过期红包回收等)。 - 插件任务:
plugin.json新增可选cron声明:
{
"name": "每日签到",
"version": "1.1.0",
"cron": {
"daily_reset": { "callback": "daily_checkin_reset", "interval": 86400 },
"monthly_stats": { "callback": "monthly_stats_run", "interval": "monthly_stats_interval" }
}
}Plugin::loadPlugins()或refresh()时同步app_cron_tasks(仿plugin_cron_sync():insert-ignore + 更新 interval/callback/enabled;manifest 删除的任务移除)。- 执行器:事务内
SELECT ... FOR UPDATE或"SELECT + UPDATE 抢占(rowCount=1)"领取 → 调用回调(容错 try/catch)→ 写日志 → 释放租约并计算下次available_at = max(now, last_started_at) + interval。回调必须幂等、可重入(租约保证同一时刻单实例执行,跨实例仍需回调自身幂等)。 - 失败处理:
failure_count+1,达retry_limit置pause_until;pause_until <= now时恢复并清零。
4.5 后台页面
- 后台"系统设置"或独立"定时任务"入口:任务列表(任务/插件/间隔/上次执行/状态/失败次数)、启用停用、立即执行、日志查看、密钥重置。
- 复用
BaseController的鉴权与 CSRF。
五、落地方案二:插件仓库(建议 P1)
核心挑战:SoSite 插件是多文件结构,市场分发必须走 zip 包,需补齐解压安全。
5.1 市场端点约定(自建或第三方静态 JSON)
GET https://plugins.sosite.example/api/plugin-market
{
"ok": 1,
"plugins": [
{
"id": "friend_links",
"name": "友情链接",
"version": "1.2.0",
"description": "首页与侧边栏友情链接管理",
"author": "SoSite",
"updated_at": 1750000000,
"size": 24576,
"sha256": "hex...", // 分发包 zip 的 sha256(强校验)
"download_url": "https://plugins.sosite.example/packs/friend_links-1.2.0.zip"
}
]
}- 本地缓存 5 分钟 + 失败回退过期缓存(仿 bbs
plugin_market_cache_*,缓存放protected/cache/)。 - 市场地址与
cron_key一样存settings表,后台可改。
5.2 分发包规范
{id}-{version}.zip
└── {id}/
├── plugin.json -- 与现有插件 manifest 一致(含 version)
├── Plugin.php
├── hook/*.php
├── views/** -- 可选
└── assets/** -- 可选5.3 安装器(app/Helpers/PluginMarket.php,新增)
流程(后台 POST + CSRF + Auth::isAdmin()):
- 下载 zip(限大小,如 10MB)→
sha256与市场声明比对,不一致拒绝; - 校验 zip 结构:
{id}/plugin.json存在、plugin.json.id与市场 ID 一致、manifest 字段白名单校验(仿plugin_manifest_validate); - 解压到临时目录,逐条目做路径穿越防护(
realpath校验每个 entry 的落点在目标插件目录内,拒绝../、绝对路径、符号链接); - 备份现有
plugins/{id}/到protected/backups/plugins/; - 原子移入
plugins/{id}/(先 rename 旧目录到备份、再 rename 临时目录到位); Plugin::refresh()重建plugins_cache.php;新插件默认停用,后台提示"已安装,请手动启用"(仿 bbs 的自动停用策略);- 写
plugin_{id}_market_meta(version/sha256/updated_at)用于更新检测。
更新 = 同流程(备份旧版后覆盖);卸载复用现有 Plugin::uninstall()。
5.4 更新检测与后台市场页
- 本地
plugin.json.versionvs 市场version,version_compare >显示"可更新"。 - 后台插件管理页新增"插件市场"tab(复用现有后台布局):列表 / 搜索 / 安装 / 更新 / 重新安装 / 卸载,按钮带二次确认。
5.5 安全红线
- 插件代码 = 站点代码权限(能执行任意 PHP)。措施:仅官方/可信市场源、zip sha256 强校验、解压路径白名单、安装后默认停用需人工启用、安装前展示版本与 sha256 摘要、
protected/backups/plugins/可回滚。 - 文档明示:第三方市场插件视为不信任代码,与 bbs 同理念。
六、落地方案三:在线升级(建议 P2)
核心挑战:SoSite 无公开 GitHub 仓库,不能照搬 GitHub API;改为自建更新服务器 + JSON 清单,比对算法从 git blob sha 换成普通 sha256。
6.1 更新源约定
GET https://updates.sosite.example/update.json
{
"version": "V5.1.0",
"published_at": "2026-08-08T10:00:00+08:00",
"message": "……更新说明(前台可展示)……",
"files": {
"app/Core/Router.php": "sha256hex",
"install.php": "sha256hex",
"index.php": "sha256hex"
},
"delete": ["assets/css/forum.css", "assets/css/layout.css"],
"sensitive": ["protected/database/init.php"] // 允许但需特别确认的文件
}- 比对:本地文件 sha256 vs 清单 → 新增 / 更新 / 删除 三类变更(
delete仅删除"本次升级声明删除"的文件,仿 bbs 的受保护路径排除)。 - 路径白名单:默认排除
config.php、protected/(数据/备份/缓存)、plugins/(插件归插件市场管)、assets/uploads/(用户数据)。需要升级的文件(如protected/database/init.php)显式列在sensitive并强确认。
6.2 升级执行(后台"系统升级"页)
复用 install.php 已验证的可靠模式,流程对齐 bbs:
- 检测:拉
update.json→ 逐文件 sha256 比对 → 展示变更清单(可勾选,提示"本地修改将被覆盖"); - 执行(POST + CSRF + 管理员 +
flock防并发):
- 逐个下载
https://updates.sosite.example/files/{version}/{path}→ sha256 强校验 → 失败即中止; - 备份全部待替换文件(含原文件 + 版本记录
protected/version-state.json)到protected/backups/upgrade/; - 原子替换(
copy→swap + rename),替换后复核 sha256; - 执行
delete清单(仅升级声明文件); - 刷新 opcache(
opcache_invalidate,已存在此模式); - 数据库结构同步:文件更新后调用
initDatabaseSchema()(SoSite 已有幂等迁移器,比 bbs 的延迟 schema 更简单——但注意新代码尚未加载的问题:应延迟到下一个请求执行,或接受"迁移器独立于业务代码"的现状,推荐前者,仿 bbs 的deferred schema); - 写版本记录
protected/version-state.json(version/sha/files/时间);
- 失败回滚:任一步失败按逆序回滚已替换文件 + 还原版本记录(仿 bbs
us_install_files的 catch 分支); - 升级通知:后台检测到新版本 →
AuditLog或站内通知提醒管理员。
6.3 后台页面
- 后台新增"系统升级"页:本地版本(
SOSITE_VERSION+ version-state)/ 远端版本 / 变更清单勾选 / 执行按钮 / 升级日志。 - 前置提示:备份数据库、附件、插件目录(复用
Admin\DatabaseController的备份入口做快捷入口)。
6.4 与现有 update 包的关系
- 保留
update/手动包作为离线兜底;在线升级是同一发布流程的自动化产物(发布方生成update.json+files/{version}/即可,可用现成的"整改包"目录做第一批发布物)。
6.5 参考结论(xiunox 全量 zip 路线,2026-08-07 复核后推荐)
对照 bbs(GitHub 逐文件 diff + git blob sha + 原子替换 + 自动回滚)与 xiunox(Gitee Release 全量 zip + 黑名单跳过 + 维护锁 + 独立步骤化迁移器)后,推荐 SoSite 走 xiunox 的全量 zip 路线:升级低频、包体小,bbs 的逐文件 diff 属过度设计;并吸收两侧安全增强:
| xiunox 做法 | SoSite 采纳 |
|---|---|
| Gitee Release(或自建源)发全量 zip,Release tag 作版本号 | ✅ 采纳;下载后 sha256 校验(xiunox 只验大小>0,我们加强) |
| 黑名单目录跳过覆盖:conf/、upload/、log/、tmp/、plugin/ | ✅ 采纳:config.php、protected/、plugins/、assets/uploads/、log 跳过 |
| 维护模式锁(写 maintenance.lock → 站点进维护页) | ✅ 采纳:升级期间入口拦截显示维护页(与 bbs 的 flock 防并发互补,锁防并发、维护锁防访问) |
| 步骤化前端编排(download→extract→db→cleanup→off)+ 单步重试标红 + 只读 diagnose | ✅ 采纳 |
| 文件覆盖后加载新代码再跑数据库迁移 | ✅ 采纳:复用现有幂等 initDatabaseSchema();可选增强"版本化步骤记录" |
| 升级后兜底递增 static_version / 清缓存 | ✅ 采纳:清模板编译缓存 + opcache_invalidate + 更新版本标记,防浏览器/模板走旧缓存 |
| 备份责任前置(流程内不自动备份,升级前强提示) | ✅ 采纳:升级前强提示,复用后台数据库备份;可加"被覆盖文件备份到 upgrade_backup_*"轻量回滚 |
| 直接 copy 覆盖不校验、SSL verify off | ❌ 不采纳:保持 zip sha256 + 覆盖后复核 + HTTPS 正规证书 |
| 无自动回滚 | ⚠️ 部分采纳:以"备份被覆盖文件"做轻量回滚,不学 bbs 的完整回滚(成本过高) |
七、实施计划
| 阶段 | 内容 | 涉及文件 | 预估 |
|---|---|---|---|
| P0 定时任务(暂缓) | 建表(并入 initDatabaseSchema)+ app/Helpers/Cron.php 执行器 + /cron 路由与 cron_key + 后台任务/日志页 + AuditLog::purgeOldLogs 接入 + 1 个插件(签到)接入 cron 声明 | 表结构、Cron.php、CronController×2、后台视图×2、Plugin.php 同步逻辑、plugin.json 示例 | 2~3 天(暂不实施) |
| P1 插件仓库 | 市场端点约定文档 + app/Helpers/PluginMarket.php 安装器(zip 校验/解压/备份/移入)+ 后台市场页 + 打包发布脚本(生成 zip + sha256 + feed JSON) | PluginMarket.php、Admin 控制器、市场视图、发布脚本 | 2~3 天 |
| P2 在线升级 | 全量 zip 发布包(Gitee Release 或自建源)+ app/Helpers/Upgrader.php(下载/sha256 校验/解压/黑名单跳过/覆盖/备份回滚)+ 维护模式锁 + 步骤化编排与只读诊断 + 复用 initDatabaseSchema() | Upgrader.php、Admin 控制器、升级视图、维护页拦截、版本记录 | 3~4 天 |
| P3 收尾 | 部署文档(cron 触发方式、市场/更新源配置)、回归测试、install.php 联动(安装时生成 cron_key) | README/部署说明、install.php | 1 天 |
合计约 1.5~2 周(单人)。三项相互独立,可任选顺序;当前建议:插件仓库 → 在线升级;定时任务暂缓(2026-08-07 决策)。
八、风险与安全措施
| 风险 | 措施 |
|---|---|
| 远程代码执行面扩大(升级/插件安装即执行远端代码) | 仅管理员 + CSRF + 强 sha256 校验 + 官方/可信源 + 安装后默认停用 + 全量备份可回滚 + 升级/安装全程日志(AuditLog) |
| zip 解压路径穿越 | 逐 entry realpath 落点校验、拒绝 ..//绝对路径/符号链接、大小与条目数上限 |
| 定时任务被滥用(恶意高频触发) | cron_key 校验 + 租约防并发 + 回调幂等 + pause 机制;CLI 方式可选 |
| 升级覆盖本地定制 | 变更清单逐文件勾选 + 升级前自动备份 + 提示本地修改将被覆盖 |
| 升级中代码/数据库不一致 | 延迟 schema 同步(新代码就绪后的下一请求执行)+ 迁移器幂等 |
| 与现有 14 个插件兼容 | 不改插件目录结构;cron 声明为增量字段;市场分发包保持现有 manifest 格式 |
| 失败任务永久阻塞队列 | retry_limit + pause_seconds 暂停-恢复机制(仿 bbs) |
九、附录
9.1 关键新增文件总览(建议)
app/Helpers/Cron.php
app/Helpers/PluginMarket.php
app/Helpers/Upgrader.php
app/Controllers/CronController.php
app/Controllers/Admin/CronController.php
app/Controllers/Admin/UpgradeController.php
app/Controllers/Admin/PluginMarketController.php
app/Views/admin/cron.php
app/Views/admin/cron_logs.php
app/Views/admin/upgrade.php
app/Views/admin/plugin_market.php
cli.php (可选,CLI 定时任务入口)
protected/version-state.json (升级版本记录,运行时生成)
protected/backups/plugins/ (插件安装/升级备份目录)
protected/backups/upgrade/ (程序升级备份目录)9.2 需复核的 SoSite 内部接口(实现前确认)
- 路由注册位置与方式(
index.php/app/init.php/Router调用点); app/Helpers/Plugin.php的loadPlugins()/refresh()缓存重建机制(plugins_cache.php),确定 cron 同步的挂载点;settings表读写封装(Settings::get/set)以存放cron_key、市场地址、更新源地址;initDatabaseSchema()的扩展方式(新增建表步骤的接入点);- 站内通知实现(
AuditLog或消息表)用于"新版本/新插件更新"提醒。
9.3 参考对照(bbs 源函数 → 本方案对应)
| bbs 函数 | 本方案对应 |
|---|---|
Setup::us_remote_release / us_local_changes | Upgrader::fetchManifest / Upgrader::diffFiles |
Setup::us_install_files(备份/原子替换/回滚) | Upgrader::apply(同模式,sha256) |
Setup::us_acquire_lock / us_run_deferred_schema | Upgrader::acquireLock / Upgrader::runDeferredSchema |
Plugin::plugin_market_install / plugin_registry_sync | PluginMarket::install / 现有 Plugin::refresh() 扩展 |
Cron::cron_run / cron_task_claim / plugin_cron_sync | Cron::run / Cron::claim / Plugin::syncCronTasks |