插件发版前最后一遍"扫雷":我把权限、路由、菜单、配置拆成四张卡片,用"假设失败"法逐项逼问

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
插件开发 145 浏览 0 回复

上周把一个内部工具插件推到预发布环境,结果测试账号点进后台直接白屏——原因是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 文本域、资产文件哈希缓存这些我这张清单还没覆盖到的,欢迎补。

评论0
回复 · 0
还没有回复
微信客服 微信客服