本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:合并篇(见正文内 merged_from 列表)。
market_hub 插件 · 实施计划、功能扩展与 P0–P3 验收报告
本篇由 3 篇原始文档合并去重而成:实施计划 + 功能扩展方案 + P0–P3 验收报告。
对象
plugins/market_hub(v1.2.1),全部改动在插件目录内闭环,未动核心。
一、插件定位
market_hub 是 FlintHub 的跨站插件商店(Hub 侧),负责把社区里的插件集中收录、展示、投稿与审核上架:
- 前台:
/plughub列表页、/plughub/{name}详情页、/plughub/submit投稿页、/plughub/shot/{id}预览图取图。 - 后台
/admin/market-hub:原 6 个 Tab(queue / listed / off / tokens / stats / settings,页内自建导航),验收后新增developers、shots。 - 数据落库
data/market/market.sqlite;公开清单release/plugins/index.json由AdminController::rebuild()重建。
与其他插件(adslot / notice_center 等)的关系:它们是被收录、被上架的「商品」,market_hub 是「货架」。market_hub 自身不提供广告、公告等业务能力,只做插件的目录、审核、认证与讨论。清单层 HubCatalog::entries() 读各插件的 manifest,meta.author 等字段是快照(上架时取 username($uid),非外键)。
二、实施计划要点
2.1 五条不变原则
| # | 原则 | 保证方式 |
|---|---|---|
| 1 | 不动核心,插件目录内闭环 | 唯一例外是 mh_submissions 加列(走插件自己的 ensureColumns()) |
| 2 | UGC 不存 manifest | 预览图 / 留言 / 开发者档案全部独立表,清单层只做合并读取 |
| 3 | 不新增 XSS 出口 | 留言正文纯文本 → htmlspecialchars();不调 Content::decode() |
| 4 | i18n 三语同步 | 基线各 138 键(验收时已到 237 键);验收 = 键集合零差异 + 键序一致 |
| 5 | CSS 只用真实存在的 --mn-* | --mn-danger / --mn-radius-* 不存在,禁用 |
2.2 分期总览
| 期 | 内容 | 新增表 | 独立出包 |
|---|---|---|---|
| P0 | 文件树 | — | ✅ |
| P1 | 预览图(详情页管理 + 投稿上传) | mh_screenshots | ✅ |
| P2 | 插件留言板 | mh_comments | ✅ |
| P3 | 管理员代绑(开发者认证) | mh_developers | ✅ |
每期结束 → 出验证报告 → 确认 → 再进下一期。
2.3 数据表(全部在 data/market/market.sqlite)
CREATE TABLE IF NOT EXISTS mh_screenshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
plugin TEXT NOT NULL DEFAULT '',
rel_path TEXT NOT NULL DEFAULT '', -- 裸相对路径
sort_order INTEGER NOT NULL DEFAULT 0,
added_by INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_mhs_plugin ON mh_screenshots (plugin, sort_order);CREATE TABLE IF NOT EXISTS mh_comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
plugin TEXT NOT NULL DEFAULT '',
uid INTEGER NOT NULL DEFAULT 0,
body TEXT NOT NULL DEFAULT '', -- 纯文本,≤ 500 字
reply_to INTEGER NOT NULL DEFAULT 0, -- 引用的留言 id(0 = 不引用)
reply_name TEXT NOT NULL DEFAULT '', -- 引用时的对方名快照
status INTEGER NOT NULL DEFAULT 0, -- 0 正常 / 1 已删除(软删除)
created_at INTEGER NOT NULL DEFAULT 0,
deleted_at INTEGER NOT NULL DEFAULT 0,
deleted_by INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_mhc_plugin ON mh_comments (plugin, id);
CREATE INDEX IF NOT EXISTS idx_mhc_uid ON mh_comments (uid, created_at);CREATE TABLE IF NOT EXISTS mh_developers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
author_key TEXT NOT NULL UNIQUE, -- manifest 的 meta.author 原文
uid INTEGER NOT NULL DEFAULT 0, -- 绑定到的用户(0 = 未绑)
verified INTEGER NOT NULL DEFAULT 0, -- 0 未认证 / 1 已认证
title TEXT NOT NULL DEFAULT '', -- 认证称号
intro TEXT NOT NULL DEFAULT '', -- 简介
verified_at INTEGER NOT NULL DEFAULT 0,
verified_by INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_mhdv_uid ON mh_developers (uid);mh_submissions加一列shots(TEXT DEFAULT '',JSON 数组):投稿携带的预览图路径,上架时搬到mh_screenshots。- 验收版实际落库
MarketDb::SCHEMA_VERSION = 2,库共 11 张表。 - 建表纪律:
CREATE TABLE在前 → 索引 DDL 在后 → 补列走ensureColumns();SQLite 无ADD COLUMN IF NOT EXISTS,靠PRAGMA table_info判定;一律走MarketDb::pdo()(内部DBFactory),禁裸 PDO(WAL 旧快照坑);改完记得手删protected/.market_hub_schema*.ok戳文件(验收版已改为带版本戳名,不再需要人工清)。
2.4 目录结构与关键文件
plugins/market_hub/
├── FrontController.php / AdminController.php # 前台 / 后台
├── MarketDb.php / HubCatalog.php / Plugin.php # 存储 / 清单 / 常量(SUB_SHOTS)
├── views/front/{detail,list,submit}.php
│ └── _file_tree.php / _shots.php / _comments.php # 新增片段
├── views/admin/index.php # queue/listed/.../developers/shots Tab
├── assets/style.css / admin.css / front.js
├── hook/route_register.php / admin_route_register.php
│ hook/init_after.php / layout_head_end.php
└── lang/{zh,en,zh_tw}.php2.5 核心流程
- 投稿:
submitPost()最开头做CONTENT_LENGTHvspost_max_size预检(否则包过大被 PHP 静默整包丢弃、$_POST/$_FILES全空,误报err=bad_name「插件名非法」);多文件$_FILES['shots']是转置结构(name[]/tmp_name[]/size[])必须逐索引重组,不能当单文件用(否则静默只处理第一张);上限 5 张、单张 ≤2M,成功者写入mh_submissions.shots(json_encode)。 - 上架:
AdminController::review()approve 时读$sub['shots']逐条INSERT INTO mh_screenshots;验收版改为仅当投稿带 shots 才replaceAll()(否则不带图的新版本上架会清掉管理员手工维护的图)。 - 文件树:
FrontController::buildFileTree(array $files): array按/切分path(数据源$manifest['files'],每项{path, sha256, size})建嵌套树;目录默认折叠、顶层展开,每目录显示「N 个文件 · 合计 X KB」;单目录超 50 条折叠;末节点按扩展名给图标(.php→code、.css/.js→code、图片→image、.md/.txt→text、.json→code,其他→file);递归深度兜底;path仅$this->e()输出,零 XSS 面。 - 详情页管理态判定:
\app\Helpers\Auth::isAdmin()(role==='admin'且group_id===4,AND 非 OR)。
2.6 路由与钩子
- 前台(
hook/route_register.php):POST /plughub/{name}/shots(增/删/排序)、POST /plughub/{name}/comment(发表)、POST /plughub/{name}/comment/{id}/delete(软删)。
⚠️ 必须注册在通配 GET /plughub/{name} 之前(与 /plughub/submit 同一个坑,route_register.php:26-29 有先例);前缀绝不能用 /plugins(站点根有同名真实目录 → 301 → 403 且丢 query)。
- 后台(
hook/admin_route_register.php,不带/admin前缀):POST /market-hub/dev-save、/market-hub/dev-verify、/market-hub/dev-sync(P3),POST /market-hub/shots-purge(补充)。
⚠️ 多 Tab 后台必须页内自建导航(核心后台卡片只认单个 admin_url),Tab 用核心 .admin-tabs。
hook/init_after.php:建表守卫,戳名带版本protected/.market_hub_schema_v{SCHEMA_VERSION}.ok。hook/layout_head_end.php:注入front.js(defer+filemtime破缓存)。- 所有写动作必带
Csrf::verifyOrDie();PRG 回跳($this->back('developers', 'msg', ...))。 - 后台保存类表单禁用
(int)($_POST[$k] ?? 0)(字段缺失会静默写最小值 → 302 成功但库里没变),用array_key_exists()判断。
2.7 环境硬约束(本机实测)
| 项 | 值 |
|---|---|
upload_max_filesize | 2M |
post_max_size | 8M |
max_file_uploads | 20 ✅ |
Plugin::maxUploadBytes()(包体上限) | 5120 KB ≈ 5M |
⇒ 包体 5M + 5 张图(单张最多 2M)= 最多 15M > post_max_size 8M。三条措施:①服务端 CONTENT_LENGTH 前置检测(必做,新增错误文案 e_too_large_total 三语);②表单前端累加 file.size 校验;③生产宝塔把 post_max_size 调 16M、upload_max_filesize 调 4M(验收时确认环境为 20M/50M,未改任何 PHP 配置)。
2.8 关键接口(实施时直接照用)
| 接口 | 位置 | 说明 |
|---|---|---|
Upload::file($file, $allowed, $opts) | Upload.php:446 | $opts['namespace']/$opts['thumb'];自带白名单 + finfo MIME 实测 |
Upload::url($rel) / Upload::thumbUrl($rel,'md_') | Upload.php:50 / :352 | 原图 / 缩略 URL,禁拼 UPLOAD_URL |
Upload::delete($rel) | Upload.php:255 | 连派生缩略图一起删,禁自己 unlink 原图 |
RateLimiter::check($action,$max,$win) | RateLimiter.php:225 | true = 允许,判断前必须有 ! |
Auth::isAdmin() | Auth.php:20 | role==='admin' 且 group_id===4 |
User::batchGetNames([$uid]) | Models/User.php | → [uid => name] 防 N+1 |
HubCatalog::entries() | HubCatalog.php:76 | 清单条目(含 author / author_uid) |
MarketDb::ensure() / ensureColumns() | MarketDb.php:53 / :190 | 建表 / 补列(幂等) |
$this->include('_components/image_lightbox') | TemplateCompiler.php:284 | 插件视图可 include 核心组件(回落 app/Views/) |
$this->pagination($page,$pages,$url) | 核心 | 分页($pages<=1 返回空串) |
三、功能扩展方案(调研版结论与去向)
3.1 决定一切的那条发现
manifest 的 meta 是「每次打包重建」的,UGC 存进去必丢。
证据链:cli/release_build.php:1253-1272 —— --type=plugin 打包时 meta 完全由 plugin.json 重建,字段固定 8 个(title/author/description/category/icon/admin_url/min_core/tag),没有 screenshots / author_uid / changelog;实测 55 个 manifest,author_uid、screenshots 有值数均为 0。反证:daily_checkin 走过 Hub 投稿上架(AdminController.php:127 确实写了 author_uid=1),但它的 manifest 现在 author_uid 也是空——被后来的官方重打包冲掉了。
⇒ 预览图、认证、留言、开发请求,一律不得只存 manifest,必须独立表/独立文件,再由清单层合并输出。
3.2 现状盘点(带证据)
- 文件罗列
views/front/detail.php:82-92:平铺、硬上限 60 条超出折叠成+N,数据源$manifest['files']({path, sha256, size},已实测)。 - 预览图「管道铺了一半」:
HubCatalog.php:253,256清单层已在读author_uid/screenshots,但 Hub 上架器(:640-652,meta 白名单里没有)与官方打包器都不写、详情页全文无引用、mh_submissions无该列(MarketDb.php:102-124)。 - 灯箱零成本复用(已核契约):入口
<a data-action="view-image">+<img data-fullurl>(lightbox.js:317,缺一点不开);lightbox.js全页收集[data-fullurl];组件与脚本在插件视图里必须自带(只在thread/show.php/blog/detail.php全局加载),禁进 htmx 片段;CSP(Controller.php:80)img-src 'self' data: https:放行站内图。 - 评论:论坛
/forum/category/6(「反馈修复」)已有 30 帖,标题惯例[BUG提交]/[优化提交];topic_index无 tag 列 → 按插件聚合需跨库两段查。 - 开发者当前没有实体:
meta.author是自由文本(55 个 manifest 实测分布:FlintHub 36 / SoSite 16 / 读到书院 1 / 读到 1 / vncom 1),author_uid有值 0/55;users表无任何认证/开发者字段;无/user/{id}路由(只有/profile)。 - 后台 6 Tab 与页内自建导航:
views/admin/index.php:92-98。
3.3 调研版 → 定稿的差异(以实施计划为准)
| 项 | 调研版 | 定稿 |
|---|---|---|
| 评论 | 方案 C:复制模板 + 跳 /post/new?category_id=6 + 详情页只读展示反馈帖 | 自建「插件留言板」(独立存储、一层引用、不做楼中楼) |
| 认证 | 表 + 后台 + L1/L2 标签,L3 全站需改核心 | 只做后台「管理员代绑」;仅 L1+L2 两处标签;别名归并不做 |
| 预览图 | 仅详情页管理 | 增加「投稿时一起上传」,审核上架后自动关联 |
| 分期 | P0 文件树 → P1 预览图 → P2 评论(方案C) → P3 认证 → P4 开发者主页 | P0 → P1 → P2 留言板 → P3 代绑,每期做完停、报验收、再进下一期 |
| 不做 | — | 开发请求、开发者主页、前台认领、全站标签、评论走论坛 |
调研版的「预览图落盘推荐走核心 Upload::file()(选项 A)」在实施阶段被指令取代:验收版改为自建路由 ShotImage 落盘(data/market/shots/<name>/)、不做缩略图、落盘统一尺寸。「少 1 张图」按软约束处理(0 张显示占位 + 管理员提示,不阻断上架——存量 55 个插件当时全是 0 张,硬卡会立刻全部不合规)。
四、P0–P3 验收报告
总体结论:全绿。P0 文件树 → P1 预览图 → P2 留言板 → P3 管理员代绑一次性做完,全部通过真实 HTTP 端到端验证。静态校验 0 失败;首轮冒烟 62 PASS / 0 FAIL;生产零触碰(protected/error.log 15 → 15 行零新增、库表零残留)。改动文件:新建 7 + 修改 13,MarketDb::SCHEMA_VERSION = 2(3 表 4 索引 + mh_submissions.shots 列)。
4.1 静态校验(全绿)
| 项 | 工具 | 结果 |
|---|---|---|
| PHP 语法 | php -l 全量 | 31 → 32 个文件 / 0 失败 |
| 全角标点紧跟变量 | php_var_cjk_check.php | OK(无「变量名含非 ASCII 字节」) |
| i18n 一致性 | fh_i18n_check.php | 55 PASS / 0 FAIL |
| 三语逐键对齐 | 独立脚本 | 各 212 → 214 → 215 → 236 → 237 键,键集合差异 0、键序一致,=> 全第 48 列 |
4.2 P0 文件树
- 目标:替换 60 条硬上限平铺文件区为可折叠树。
- 实现:
FrontController新增私有方法buildFileTree(array $files): array,detail()传入tree;新增views/front/_file_tree.php(递归渲染,最大深度保护);assets/style.css新增.mh-tree*;三语新增文件数/合计大小/展开折叠键。 - 验收结果:
mh-tree✓mh-tree-total✓,旧mh-filelist已清除✓;游客页无未解析 i18n 键名、无 PHP 错误;300+ 文件大插件页面不卡不超时;php -l全绿。
4.3 P1 预览图
- 目标:1~5 张、管理员详情页内联管理(增删 + 拖拽排序)+ 投稿上传 + 灯箱查看。
- 实现(新建):
ShotImage.php(14309 B,六道闸门校验 +finfoMIME 实测 + 缩放居中)、ShotRepo.php(8737 B,listOf/countOf/find/add/remove/reorder/reorder/replaceAll/renderList/urlOf)、views/front/_shots.php(5545 B);Plugin新增SUB_SHOTS='shots'常量(刻意放data/market/而非插件目录,避免卸载带走内容数据);assets/front.js拖拽dragend即提交隐藏表单(不留「保存」按钮);路由POST /plughub/{name}/shots+GET /plughub/shot/{id};灯箱契约data-fullurl+view-image齐。 - 投稿链路:
views/front/submit.php加name="shots[]" multiple(保持enctype="multipart/form-data");submitPost()先CONTENT_LENGTH预检,多文件经ShotImage::normalizeFiles()逐索引重组逐张落盘;额度按「已成功张数」推进(某张失败不占额度),库行失败回删文件防孤儿。 - 验收结果(保留实测数字):上传 302 → 库行生成 → 落盘实测 400×300(1165 B PNG) →
GET /plughub/shot/{id}200 +image/png+nosniff+Cache-Control: public→ 删除 302 → 库行回 0 → 取图 404。后续三轮迭代均全绿: - 多选上传(
shots[]+multiple):43 PASS / 0 FAIL——一次 3 张n=3&lost=0、sort_order连续 0/1/2;额度截断n=2&lost=1(超额没落盘);混选(1 合法 + 1 假.png)只落 1 行 1 文件无孤儿。 - 裁切 → 完整缩放 + 透明留边(contain):
scale由max()改min(),新增needsPad(),留边 + jpg 源改判 png;探针自带反证(源图描红边,落盘后 bbox 四角必须仍是红):34 PASS / 0 FAIL + 真实上传冒烟 29 PASS / 0 FAIL。 - 400×300 → 800×600,留边图 PNG → WebP(站长反馈「图片挺模糊」,诊断:灯箱原像素显示 + 缩放上限 6 倍 + 高分屏):体积实测 PNG 791 KB vs WebP 96 KB;探针 37 PASS / 0 FAIL + 上传冒烟 29 PASS / 0 FAIL(
Content-Type: image/webp、bbox 800×450、留白alpha=127)。 - ⚠️ 已落盘的旧裁切图不会被修复(原图无留底),只能删旧重传。
4.4 P2 留言板(后更名「讨论反馈」)
- 目标:独立存储、一层引用、纯文本、登录可发、限流、管理员可删。
- 实现(新建):
CommentRepo.php(9815 B,软删除 +reply_to一层引用——目标若本身是回复则拉平到同一条主留言 + 墓碑判定 + 批量取用户名防 N+1 +quotedMap()/snippet());views/front/_comments.php(5405 B,引用回复条 + 墓碑 + 系统分页);front.js回复按钮回填表单、删除data-confirm二次确认。 - 关键规则:
requireLogin()才能留言;RateLimiter::check('mh_comment', 5, 60)(true = 允许,写反会恒 429);正文mb_substr(trim(...), 0, 500)+htmlspecialchars(ENT_QUOTES),不调Content::decode();管理员走软删(status=1),删除动作不得改动引用方的reply_to/reply_name,被引留言删除后引用行显示「(已删除)」;10 条/页。 - 通知联动(第二轮修正):顶层讨论 → 插件作者(
mh_developers.uid),回复 → 被回复者,自己给自己不发;复用核心Notification::TYPE_REPLY(自造 type 会落通用问号图标,补图标要改核心);related_type=market_hub+#cmt-{id}深链;标题/摘要存原文不预转义(通知中心统一$this->e())。 - 验收结果:发表/引用回复/删除均 302;「回复 @某某」+ 被引正文截断 60 字(
snippet():220 字 → 60 字 +…,多行多空格折叠);软删后墓碑显示且引用行未清空;通知三场景实收正确(uid1 发 → uid262 收 1 条、uid1 收 0 条);「留言板 → 讨论反馈」三语 14 处更名(键名cmt_*不动),212 → 214 键;本轮 50 PASS / 0 FAIL。
4.5 P3 管理员代绑(开发者认证)
- 目标:把「自由文本作者名」实体化为开发者档案,后台绑定 uid + 认证 + 称号。
- 实现(新建
DevRepo.php,9648 B):AdminController::TABS加developers,新增devBind/devVerify/devSave/devSync;后台「开发者」Tab 用面板卡片式(每行 2 表单 + 2 按钮,表格列序易被核心样式压掉);首次进入自动补全——扫HubCatalog::entries()的author去重,缺失的补成verified=0行,幂等(ON CONFLICT(author_key) DO NOTHING);绑定输入 uid 或用户名,服务端校验存在;保存类表单用array_key_exists()防字段缺失静默写最小值。 - 前台仅两处标签:详情页作者名旁(
views/front/detail.php:61)、列表卡片作者名旁(list.php:92);认证时输出<span class="mh-verified">+$this->icon('check', 10)+ 称号,底色--mn-primary白字;未认证不输出任何东西;不做全站标签(无作者名钩子,需改核心)。 - 第四批增强(一人多作者绑定):
DevRepo::bind()去掉uid_taken唯一性约束——manifestmeta.author是自由文本,同一个人可能署名不同(「FlintHub」与「读到书院」);新增authorCountByUid()出「共 N 个作者名」徽标;解绑仍是行**粒度;下游 uidForAuthor()` 按作者名查 uid → 该用户在每个作者名对应的插件上都能收到讨论通知。 - 验收结果:Tab 入口✓ 4 端点✓ 开发者面板 5 个;把
SoSite(uid=0)绑到已绑FlintHub的 uid=1 →msg=dev_bound成功、不再返回dev_uid_taken;uidForAuthor('SoSite')===1且原绑定未被顶掉;不存在的用户 →err=dev_no_user且未误写 uid;mh_developers逐行逐列还原(5 行);33 PASS / 0 FAIL。环境事实:FlintHub已被站长绑到uid=1(本人)且已认证——站长在自己插件下发讨论不会收到通知(自己给自己不发)。
4.6 顺手修掉的缺陷与补丁
- 建表守卫戳文件名写死(本轮唯一新增代码改动,静默缺陷):
hook/init_after.php原用固定名protected/.market_hub_schema.ok且只判is_file()、不看SCHEMA_VERSION→ 老站覆盖上传新版插件目录(该路径不触发activate(),正是该钩子存在的理由)→MarketDb::ensure()被跳过 → 3 张新表与shots列永不创建 → P1/P2/P3 全部 500,且error.log干净。修法:戳名带版本protected/.market_hub_schema_v2.ok,版本号一加戳名即变、自动补建,不再需要人工清戳。验证:删新旧两个戳 → 一次GET /plughub→ 只生成 v2 戳、HTTP 200、11 张表齐。 - 「重建公开清单」成功无提示:
rebuild()回跳msg=rebuilt&n=<收录数>,但视图$msgMap没有rebuilt项 → 横幅整块不渲染(「点了没反应」)。补丁:$msgMap加'rebuilt' => 'plugin.market_hub.m_rebuilt'(三语同位插入m_settings_saved之后,236 → 237 键),新增mh_rebuilt_smoke.sh20 PASS / 0 FAIL(跟随Location跳转后断言「公开清单已重建,共收录 {n} 个插件」、release/plugins/index.json按字节还原sha256未变)。同类但症状更轻的rebuild_failed:<详情>裸显未改。 - 后台孤儿预览图清理(
ShotAudit.php新):三类独立——孤儿文件(磁盘有、白名单没有)unlink()、将变空的目录(rmdir(),逐个列出)、失效记录(DELETE)。★ 白名单必须含mh_submissions.shots:投稿时上传的图立刻落盘、路径只写投稿行,等审核才搬进mh_screenshots——若白名单只查后者,所有待审投稿的图都会被误判孤儿删掉。删除纪律:服务端重扫(不接受前端清单,防篡改 + TOCTOU)、删除前再断言路径在 shots 根内、不跟符号链接、跳过点开头条目。mh_p9_smoke.sh57 PASS / 0 FAIL + CLI 探针(A/B 反证:把探针路径写进投稿行的 shots → 不再判孤儿,撤掉 → 立刻重新判孤儿)22 PASS / 0 FAIL。
4.7 与方案的偏差(择要)
post_max_size 未改配置(服务端预检 + 专用文案兜底);预览图自建 ShotImage 而非核心 Upload::file();单张上限 2M;留言用户可自删(原定只管理员删);分页 10 条/页;错误文案合并为 3 个语义键;开发者 Tab 用面板卡片式;replaceAll() 仅当投稿带 shots 才执行。
五、遗留与后续
| # | 事项 | 状态 |
|---|---|---|
| 1 | 撤销/驳回投稿的预览图清理 | 后台已做 ShotAudit 按需清理;按答复「先保留」,未做自动任务 |
| 2 | 别名归并(flinthub / FlintHub 算两个档案行) | 未做(本期范围外) |
| 3 | 全站认证标签(需核心作者名钩子) | 未做;仅插件详情页 + 列表页两处 |
| 4 | 开发请求 / 开发者主页(调研版 P4) | 本期不做 |
| 5 | 出包 | P0~P3 未打包;protected/ 与 data/market/ 均不进包 |
| 6 | 旧预览图兼容 | 已落盘的 400×300 裁切旧图不会被修复,需删旧重传;aspect-ratio: 4/3 保证新旧混排一致 |
| 7 | rebuild_failed:<详情> 错误串裸显 | 未改,待决定是否做「译文 + 详情」两段式 |
| 8 | 生产 CDN 对 ?v=filemtime 静态资源的缓存 | 待确认 |
探针/冒烟经验(易踩):curl -F "name=@路径" 的路径必须写 D:/...(MSYS 不转换);i18n 断言必须限定目标面板区域(window.FlintHubI18n 在 <head> 注入所有插件的键,全页 grep 必假 FAIL);「回到基线」断言必须在制造现场之前采基线;冒烟脚本注意 Windows \r 污染(pyc() { "$PY" -c "$1" | tr -d '\r'; })。
下一步:浏览器完整走查(详情页文件树 / 预览图灯箱 / 讨论引用与自删、投稿带 1~5 张图、后台开发者 Tab 绑定与认证)→ 确认后出 plugins/market_hub/ 单根 zip → 再决定是否处理清理任务与遗留项。
保留的回归套件(.workbuddy-ai/tmp/):mh_p9_probe.php(22 项)、mh_p9_smoke.sh(57 项)、mh_dev_smoke.sh(33 项)、mh_shot_contain_probe.php(37 项)、mh_shot_contain_smoke.sh(29 项)、mh_rebuilt_smoke.sh(20 项)、mh_sess_p9.php(会话夹具)。