插件发版前最后一遍"扫雷":我把权限、路由、菜单、配置拆成四张卡片,用"假设失败"法逐项逼问
上周把一个内部工具插件推到预发布环境,结果测试账号点进后台直接白屏——原因是manage_options capability 我本地用管理员测的,预发环境给的"运营"角色压根没这权限。这件事让我意识到:上线前的检查不能是"看代码觉得对",得用"如果这里坏了会怎样"的假设去逼问每个环节。
现在我把这套方法固化成四张卡片,发版前逐项过一遍,分享出来。
卡片一:权限——"最低角色是谁?"
不要问"管理员能不能用",要问"哪个角色最弱、还能合法进这个页面"。
我的做法:在register_activation_hook里显式创建 capability,而不是复用系统自带的。比如一个"查看数据报表"的功能:
// 激活时给"编辑"角色赋权,而不是默认的 admin
$role = get_role( 'editor' );
if ( $role && ! $role->has_cap( 'myplugin_view_reports' ) ) {
$role->add_cap( 'myplugin_view_reports' );
}
// 页面注册时绑定这个自定义 cap
add_submenu_page(
'myplugin-dashboard',
'数据报表',
'数据报表',
'myplugin_view_reports', // 不是 manage_options
'myplugin-reports',
[ $this, 'render_reports' ]
);
上线前检查项:新建一个只有"编辑"角色的测试账号,确认能进;再新建一个"作者"账号,确认 403 或隐藏菜单。两个都对了,这张卡片才能撕掉。
卡片二:路由——"直接 curl 会漏什么?"
REST 路由我习惯用register_rest_route包一层命名空间,但上线前容易漏的是:权限回调里current_user_can的参数和菜单注册的 capability 不一致,导致"页面能进,接口 403"或者反过来。
我的土办法:写一段"路由自审"代码,激活时跑一遍全量端点,输出到日志:
add_action( 'rest_api_init', function () {
$routes = rest_get_server()->get_routes( 'myplugin/v1' );
foreach ( $routes as $route => $handlers ) {
foreach ( $handlers as $handler ) {
$permission = $handler['permission_callback'] ?? '未设置';
error_log( "路由: {$route} | 权限回调: " . ( is_callable( $permission ) ? 'callable' : 'missing' ) );
}
}
}, 999 ); // 挂最后,确保全注册完再扫
上线前检查项:把这段代码的日志输出贴出来,逐行核对每个端点的permission_callback是不是和菜单 capability 对应。尤其注意WP_REST_Server::EDITABLE的端点,POST/PUT/DELETE 的权限要比 GET 更严格。
卡片三:菜单——"子菜单会不会"孤儿化"?
父菜单用add_menu_page,子菜单用add_submenu_page,但如果父菜单的 capability 比子菜单严格,就会出现"子菜单显示但点进去无权限"的诡异状态。更隐蔽的是:第三方插件改了$menu全局数组,把你的子菜单挤到别的父菜单底下。
我的防御代码:在admin_menu钩子末尾加一层"结构校验":
add_action( 'admin_menu', function () {
global $submenu;
$parent = 'myplugin-dashboard';
if ( ! isset( $submenu[ $parent ] ) ) {
// 父菜单存在但子菜单数组丢了,发通知或写日志
do_action( 'myplugin_menu_orphan_alert', $parent );
}
}, 999 );
上线前检查项:安装一两个目标用户可能同时用的"重菜单"插件(比如 WooCommerce、SEO 插件),确认你的菜单结构没被顶乱。子菜单顺序用$position参数显式控制,别靠默认追加。
卡片四:配置——"默认值在全新安装时会不会"空窗"?
选项表里的配置,开发时往往有残留数据,全新安装时get_option( 'myplugin_setting' )可能返回false,你的代码有没有兜底?
我踩过的坑:一个"每页显示条数"的配置,默认应该是 20,但我在代码里直接用了:
$per_page = get_option( 'myplugin_per_page' ); // 新安装返回 false,转成 0,分页炸裂
修正后:
$per_page = absint( get_option( 'myplugin_per_page', 20 ) );
if ( $per_page < 1 ) {
$per_page = 20;
}
上线前检查项:在一个全新数据库实例上走一遍安装流程,不手动改任何选项,确认每个功能都有合理默认值。卸载钩子register_uninstall_hook也要测,确认清理彻底后重新安装,数据是干净的"初态"。
最后:四张卡片串成一条命令
我把上面四项写成一个 WP-CLI 命令,发版前跑一遍:
wp myplugin preflight-check
输出大概是:
[权限] 检测角色: editor ✓ | author ✗ (预期) [路由] 端点 6 个,权限回调全部绑定 ✓ [菜单] 父菜单 myplugin-dashboard,子菜单 4 项,无孤儿 ✓ [配置] 全新安装默认值: per_page=20, enabled=false ✓
全部绿了再打包 zip。这套流程不保证不出 bug,但至少能把"灯下黑"的结构性问题挡在发版前。
你们上线前有什么独门检查项?比如 i18n 文本域、资产文件哈希缓存这些我这张清单还没覆盖到的,欢迎补。

