XIUNOX 插件上架前必看:开发者自检清单 + 应用市场审核标准

管理

1为什么需要这份文档

XIUNOX 应用市场欢迎开发者贡献插件,但很多插件提交前没有经过完整测试,导致:

  • 致命 安装后白屏、fatal error,影响整个站点

  • 严重 卸载不干净,残留表/配置污染数据库

  • 严重 hook 内 return 导致宿主函数逻辑被跳过

  • 一般 缓存不刷新,修改后不生效

  • 一般 伪静态模式下 URL 错误

本文档是上架硬性门槛,不是建议。未通过自检的插件将被审核员驳回。

文档分两部分:第一部分是开发者提交前必须完成的测试流程;第二部分是审核员依据的代码规范标准。两者结合,确保上架插件质量。

2上架前必做测试(开发者自检)

2.1 安装 / 卸载 / 升级全流程

这是最高频出问题的环节。很多开发者只测了"能装上",没测"能卸干净"和"能升级"。

全新安装测试

  • DEBUG=1(开发环境)下全新安装,无任何报错

  • DEBUG=0(生产环境)下全新安装,无 fatal error 白屏

  • 安装后插件目录的 conf.jsoninstalled=1enable=1

  • 数据库表已创建,字符集为 utf8mb4(不是 utf8)

  • 默认配置已写入 setting_get('你的插件名')

  • 安装后 tmp/model.min.php 已被清理(install.php 末尾必须清理)

卸载干净测试

  • 卸载脚本文件名是 uninstall.php(不是 unstall.php

  • 卸载后所有插件表已 DROP TABLE

  • 卸载后 setting_delete('你的插件名') 清除配置

  • 卸载后 kv_delete('你的插件名') 清除 KV 数据

  • 卸载后 XnEvent::off(null, '你的插件名') 清理事件监听(如有)

  • 卸载后 CacheHelper::pluginDeletePrefix('你的插件名') 清除缓存

覆盖升级测试

关键场景:存量用户已经安装旧版本,覆盖新代码后不会重新执行 install.php,只走 upgrade.php。

  • 数据库结构变更放在 upgrade.php(不是 install.php 或 setting.php)

  • upgrade.php 用 SHOW COLUMNS + ALTER TABLE 幂等逻辑(可重复执行不报错)

  • 结构变更后递增 conf.json.version(如 1.0 → 1.1),触发核心层「需升级」提示

  • 覆盖代码后不重新安装,直接在后台点「升级」,功能正常

启用 / 禁用切换测试

  • 禁用插件后,前台功能完全消失(无残留 JS/CSS/卡片)

  • 重新启用后,功能恢复,无数据丢失

  • 禁用/启用不影响其他插件运行

2.2 功能测试矩阵

必须在以下场景中测试核心功能,不能只测"管理员发帖"这一条路径。

测试场景

游客

普通用户

版主

管理员

前台查看插件功能

按权限

必测

必测

必测

发帖/回帖触发插件

必测

必测

必测

后台设置页保存

拒绝

必测

删帖级联清理

必测

必测

前台核心场景

  • 首页加载无 JS 报错(F12 控制台)

  • 帖子列表页加载插件 hook 无性能问题(不 N+1 查询)

  • 帖子详情页插件功能正常显示

  • 发帖页插件功能(如有)正常工作

  • 手机端布局正常(响应式)

后台设置页

  • setting.php 开头有 $gid != 1 && $gid != 2 AND message(-1, '无权限');

  • POST 表单包含 CsrfService::input() + CsrfService::check()

  • 保存后配置正确写入,页面跳转回设置页

  • 敏感字段(密码/Token)用 param($key, $default, FALSE) 关闭转义

多语言测试

  • 新增语言键同时写入 hook/lang_zh_cn_bbs.php(不只写 lang/ 目录)

  • 切换到 zh-tw / en-us 语言包,插件文案正常显示

  • 未翻译的键显示为 lang[key] 字面量时,说明语言键未生效

语言键只写 lang/zh-cn.php 不写 hook/lang_zh_cn_bbs.php 是高频错误。前台 lang() 实际读 tmp/lang_zh_cn_bbs.php 编译缓存,不读 lang/ 目录。

2.3 缓存与编译测试

XIUNOX 的 _include() 编译缓存不比较源文件 mtime,这是最容易踩的坑。

编译缓存测试

  • 修改 route/*.php / model/*.func.php / view/htm/*.htm 后,删除 tmp/ 下对应编译缓存

  • 批量清理命令:rm -f tmp/route_*.php tmp/model_*.func.php tmp/view_htm_*.htm tmp/lang_*_bbs.php

  • tmp/cache/ 无效,必须清 tmp/ 根目录下的编译文件

数据缓存测试

  • 数据变更后缓存自动刷新(用版本号机制或主动删除)

  • 清除插件缓存用 CacheHelper::pluginDeletePrefix('插件名'),不枚举 limit 值

  • 缓存键通过 CacheHelper::pluginKey() 生成带 p_{plugin}_ 前缀

  • Service 类构造函数调用 CacheHelper::registerKeys() 注册缓存键

CacheHelper::set() 写裸值陷阱remember() 内部用 array('__v'=>$data) 哨兵包装,set() 写裸值(如版本号整数)会被 remember() 判为 MISS,导致版本号 bump 永久失效。必须统一用 array('__v'=>$value) 哨兵格式。

2.4 兼容性测试

运行环境

  • PHP 8.0+ 无弃用警告(deprecated notice)

  • MySQL 5.7+ / MariaDB 10.3+ 兼容

  • 数据库表用 utf8mb4 字符集(支持 emoji)

  • Redis 缓存驱动下功能正常(不只是文件缓存)

伪静态模式

XIUNOX 支持 6 种伪静态模式(url_rewrite_on 0~5)。所有 URL 必须用 url() 函数生成,禁止硬编码 .htm 后缀。

  • 所有 URL 用 url() 或命名快捷函数(如 thread_url($tid))生成

  • 禁用 url("thread-$tid") 字符串拼接,改用 thread_url($tid)

  • 缓存刷新/跳转用 $site_url . url("xxx"),不用 $site_url . '/xxx.htm'

  • 插件自定义路由通过 hook/model_route_table_end.php 注册到 $routes 数组

  • 切换伪静态模式后,插件所有链接正常跳转

htmx 4 兼容

  • htmx 事件名用 4 格式(冒号分隔):htmx:after:swap / htmx:response:error

  • 禁用 2.x 旧名:htmx:afterSettle / htmx:configRequest(会静默失效)

  • htmx 请求返回纯 HTML 片段(不是完整页面/JSON/重定向)

3代码规范审核标准(审核员)

以下是审核员逐项检查的硬性标准。每条标注严重级别,致命 级别一票否决。

3.1 结构规范

检查项

级别

要求

conf.json 必填字段

致命

name / brief / version(X.Y.Z 三位制)/ bbs_version(X.Y 两位制,1.x 系列)

bbs_version 取值

致命

必须为 "1.0" / "1.1"禁止 "4.5"(XIUNOX 是 1.x 系列)

hooks_rank 格式

严重

对象 {"hook_name.php": 10},键名和 hook 文件名(含扩展名)完全一致

overwrites_rank 格式

严重

对象 {"view/htm/header.inc.htm": 10}不是数组

dependencies 格式

严重

对象 {"xn_search": "1.0"}不是数组

install.php 幂等

致命

CREATE TABLE IF NOT EXISTS + DEFAULT CHARSET=utf8mb4

install.php 末尾清缓存

严重

清理 tmp/model.min.php,防 Service 类未加载白屏

uninstall.php 文件名

致命

标准拼写 uninstall.php,禁止 unstall.php

upgrade.php 机制

严重

结构变更走 upgrade.php(幂等),不在 install.php/setting.php 加字段自愈代码

命名前缀

严重

表/变量/语言键/JS/CSS/setting 都带插件前缀;禁用 xn_ / xnx_ 官方预留前缀

3.2 安全规范

检查项

级别

要求

CSRF 防护

致命

所有 POST 表单含 CsrfService::input() + 后端 CsrfService::check()

输出转义

致命

esc_html() / esc_attr() / esc_js(),禁止裸 htmlspecialchars

敏感字段 param()

致命

密码/Token 用 param($key, $default, FALSE) 关闭 htmlspecialchars,否则含 & < > " 的密码验证失败

后台权限检查

致命

setting.php 开头 $gid != 1 && $gid != 2 AND message(-1, '无权限');

SQL 注入防护

致命

优先 db_find / db_find_one;保留 JOIN/复杂 SQL 需加 // 保留 db_sql_find 注释

用户输入 intval

严重

SQL 拼接用户输入必须 intval() 转义

入口级安全检查覆盖

严重

IP 黑名单/封禁检查覆盖所有入口(login/create/resetpw/发帖/回帖/编辑)

3.3 前端规范

检查项

级别

要求

禁用 jQuery

致命

新代码禁用 $() / .on() / .ajax(),用 XN.toast() / XN.ajax() / XN.confirm()

禁用 Alpine.js

致命

禁用 x-data / x-show / x-bind / x-model

htmx 4 事件名

致命

冒号分隔:htmx:after:swap,禁止 2.x 旧名 htmx:afterSettle

Bootstrap 5 + Tabler Icons

严重

用 BS5 组件 + <i class="ti ti-xxx"></i> 图标

x-card 卡片样式

严重

卡片加 x-card class,禁用裸 border / border-bottom 分隔列表项

JS/CSS 存放位置

致命

static/js/ / static/css/,禁止放 view/htm/

资源引用路径

严重

$conf['view_url'] . '../plugin/你的插件/static/js/xxx.js',禁止 APP_PATH 或相对路径 ../

后台禁用 htmx

严重

admin/ 目录下禁止用 htmx

状态不放 window

严重

禁用 window.__xxxData,状态放 DOM(data-* / hidden input)

3.4 数据与 SQL 规范

display_name 是虚拟字段:由 user_format() 在 PHP 运行时从 nickname + username 派生,不是数据库字段。SQL 中 SELECT display_name 必触发 1054 Unknown column

检查项

级别

要求

用户信息获取

致命

user_find_by_uids() / user_read() / user_read_cache(),禁用 db_find('user') 后取 username

显示用户名

致命

模板统一取 $user['display_name'],禁用 $user['username']

UNIQUE 字段批量写入

严重

username / nickname / email 是 UNIQUE 索引,批量操作用 deleted_{uid} 后缀,禁止统一文案

counter 递减保护

严重

user_dec / forum_dec / thread_dec(含 GREATEST(field-N, 0)),不用裸 db_array_to_update_sqladd

分页过滤下沉 SQL

严重

过滤条件下沉到 SQL 层,防止分页后过滤导致条数不一致

db_insert 返回值检查

严重

db_insert 后检查返回值,字段不存在会静默失败

3.5 hook 规范

所有 hook 文件禁止 return(已违反 4 次,最高优先级):hook 编译期内联到宿主,return; 会从宿主返回,跳过后续逻辑。违规示例 if (!class_exists('X')) return; → 改为 if (class_exists('X')) { ... } 包裹。

检查项

级别

要求

hook 禁止 return

致命

所有 hook 文件(路由+model+view 层)禁用 return;,用 if 包裹整个逻辑

终止性 exit 注释

严重

API 响应/重定向后允许 exit;,但必须 if 包裹 + // ponytail: 注释说明

PHP hook 开头

致命

PHP hook 以 <?php exit; 开头(防直接访问)

HTM hook 开头

致命

HTM hook 以 <?php 开头,禁止 <?php exit;(会白屏)

hook 文件名匹配

致命

文件名(含扩展名)必须和源码标记一模一样:// hook xxx.phpxxx.php

发帖/回复场景区分

严重

共用 post.htm 的 hook 加 if ($route == 'thread' && $action == 'create') 判断

发帖页功能挂载点

严重

挂到 post_ref_thread_after.htm(PC 右侧栏),禁止 post_subject_after.htmpost_message_after.htm

列表页 hook 性能

严重

Service::getCached($uid) 静态缓存,禁止 new Service() + 查库 N 次

删帖级联清理

严重

注册 model_thread_delete_end.php / model_post_delete_end.php 清理插件数据

Service 类加载

严重

调用核心 Service(CreditsService 等)前 if (!class_exists('X')) { include_once ... } 守卫

前台判断插件启用

严重

plugin_paths_enabled() 或读 conf.json,禁用 global $plugins(前台未初始化)

3.6 缓存规范

检查项

级别

要求

新增缓存 API

严重

CacheHelper::remember($key, $ttl, $callback, $plugin),禁手写 cache_get/cache_set 样板

清理缓存 API

严重

CacheHelper::pluginDeletePrefix($plugin),不枚举 limit 值

缓存键前缀

严重

CacheHelper::pluginKey($key, $plugin) 生成 p_{plugin}_ 前缀

缓存键注册

严重

Service 构造函数调用 CacheHelper::registerKeys($plugin, $keys)

set() 哨兵格式

致命

CacheHelper::set() 写版本号用 array('__v'=>$value),禁裸值

长键处理

注意

长键(>32 字符)会被 md5 哈希,deleteByPrefix 匹配不到,改用版本号机制

cache_truncate

致命

禁用 flushdb,用 deleteByPrefix

4高频踩坑清单

以下是 XIUNOX 项目实战中已发生事故的规则,按违反次数降序排列。必读 修改前必须逐项核对。

4 次以上(最高优先级)

hook 文件禁止 return — 已违反 4 次(xnx_feeds/xnx_hidden 路由层 + xnx_feeds model 层 6 个 + xnx_maintenance runlevel 检查被跳过严重 bug)。终止性操作允许 exit 但必须 if 包裹 + // ponytail: 注释说明。

2 次(高优先级)

  • 修改 PHP 核心函数时同步 min.php — 已违反 2 次

  • 表单提交前禁止 jform.reset()(清空用户输入)— 已违反 2 次(admin_login + 6 个后台模板)

  • 密码等敏感配置 param() 必须传第 3 参 FALSE — 已违反 2 次(登录失败 + admin user_create/update)

  • display_name 是虚拟字段,SQL SELECT 必报 1054 — 已违反 2 次,影响 6 个插件

  • 分页过滤前移遗漏 post 表 JOIN — 已违反 2 次

  • xn.* 函数库迁移漏迁xn.url / xn.is_number)— 已违反 2 次

  • $.ajax / $.param jQuery 静态方法迁移漏迁 — 已违反 2 次

1 次(需关注)

  • 更新日志误写到子目录或 Glob 递归搜索

  • lib 类顶部 include _include() 函数库导致重复声明 fatal

  • JS 迁移漏迁全局符号(setBtnLoading + 死模板)

  • htmx 事件名用 2.x 旧名(htmx:configRequest)导致静默失效

  • 修改 _include() 加载的文件后未删 tmp/ 编译缓存

  • URL 函数双重包裹(url(admin_plugin_setting_url($dir))

  • 插件语言键未同步写入 hook/lang_*_bbs.php

  • conf.phpstatic_version 未在 JS/CSS 修改后递增

  • overwrites_rank 覆盖模板放在 view/htm/ 而非 overwrite/

  • 核心 Service 静态成员访问前缺少 class_exists 守卫

  • 入口级安全检查未覆盖全部路由

  • CacheHelper::set() 写裸值与 remember() 哨兵格式不匹配

  • lib 类不会自动加载(路由层必须显式 include

5提交材料清单

5.1 必需文件

  • conf.json — 字段完整,version 用 X.Y.Z 三位制,bbs_version 用 X.Y 两位制

  • install.php — 建表 + 默认配置 + 末尾清 model.min.php

  • uninstall.php — 标准拼写,删表 + 删 KV + 删 setting

  • upgrade.php — 如有数据库结构变更必须提供,幂等

  • setting.php — 后台设置页(如有配置项)

  • README.md — 插件说明、功能介绍、使用方法、截图

5.2 代码文件

  • model/XxxService.php — Service 类(静态方法模式)

  • hook/model_inc_file.php — 注册 Service 类路径

  • hook/ 下所有 hook 文件(文件名和源码标记一致)

  • route/ 路由文件(如插件有独立页面)

  • view/htm/ 模板文件

  • static/js/ / static/css/ 静态资源

5.3 测试报告

提交时请附上测试报告,包含:测试环境(PHP 版本/MySQL 版本/伪静态模式)、测试流程(安装→功能→卸载)、测试结果截图。

  • DEBUG=0 生产环境安装截图

  • 核心功能正常截图(前台+后台)

  • 卸载后数据库干净截图(SHOW TABLES 无插件表)

  • 多语言切换截图(zh-cn / zh-tw / en-us)

  • 手机端布局截图

5.4 打包规范

  • zip 包根目录是插件目录名(如 my_plugin/

  • 不包含 .git / .DS_Store / tmp/ 等无关文件

  • 不包含 conf.jsoninstalled / enable 字段为 1(必须是 0)

  • 文件名用下划线 _,不用连字符 -

最新回复

请先登录后再回复 登录

uid:1 管理
关注
随遇而安,随缘而行
发帖 98
评论 379
粉丝 12
关注 1
发新帖
目录
XIUNOX 插件上架前必看:开发者自检清单 + 应用市场审核标准