上线前夜被运营@了三次:一份能救命的插件发布 Checklist,以及我漏掉 `manage_options` 后让订阅用户看见导出按钮的惨案
上周三凌晨两点,运营在群里甩了张截图:一个刚注册的订阅者账号,后台侧边栏赫然挂着「数据导出」菜单,点进去还能正常下载全站用户 CSV。我当场清醒——不是黑客入侵,是我本地开发时用的管理员账号,上线前根本没切角色测一遍权限矩阵。
这事儿逼我整理了一份「发布前必过清单」,不整虚的,直接按执行顺序列,附带我自己或同事真踩过的坑。
一、权限节点:别只测管理员
WordPress 的 `add_menu_page` / `add_submenu_page` 里那个 `capability` 参数,很多人填 `manage_options` 就完事。但你的功能真的需要这么高吗?反过来说,你以为低权限进不来,可能漏了「直接访问 URL」这条路。
我现在的做法:
```php // 1. 菜单注册用最小权限 add_submenu_page( 'zsens-admin', '数据导出', '数据导出', 'export_private_data', // 专用能力,而非 manage_options 'zsens-export', 'zsens_render_export_page' ); // 2. 页面内部再验一次,防 URL 直闯 function zsens_render_export_page() { if ( ! current_user_can( 'export_private_data' ) ) { wp_die( '你小子怎么进来的?', 403 ); } // 业务逻辑... } ```Checklist 项:准备三个测试账号——管理员、编辑、订阅者,逐个扫一遍所有菜单入口和直接 URL。
二、路由注册:REST 端点的「暗桩」
`register_rest_route` 的 `permission_callback` 是最后一道门,但很多人写成这样:
```php 'permission_callback' => '__return_true' ```开发时图方便,上线前忘了改。更隐蔽的是命名空间冲突——两个插件都注册了 `/wp-json/zsens/v1/data`,后加载的会覆盖先加载的,返回 200 但数据结构全错。
我现在上线前必跑:
```bash wp rest route list --format=json | jq '.[] | select(.namespace | contains("zsens"))' ```核对每条路由的 `methods`、`callback`、`permission_callback` 三要素。
三、菜单层级:slug 的「吞并」陷阱
之前发过一篇 slug 撞车导致 403 的,这里补另一个角度:父菜单和子菜单的 `menu_slug` 如果重复,WordPress 不会报错,但子菜单会直接「吸附」到另一个父菜单下,你的设置页可能出现在完全不该出现的顶级菜单里。
我的命名规则:顶级菜单用 `zsens-{module}`,子菜单用 `zsens-{module}-{action}`,绝不让任何两个 slug 的前两段重复。
四、配置持久化:update_option 的「缓存幽灵」
这个坑之前也单开过帖,但 Checklist 里必须再提。上线前确认:
- 配置页保存后,对象缓存(Redis/Memcached)是否同步失效?
- 多站点环境下,`update_blog_option` 和 `update_site_option` 有没有用混?
- 配置项的 `autoload` 是 `yes` 还是 `no`?高频读的配置才配 `yes`,否则拖慢每请求。
我加了个调试开关,非生产环境保存配置时自动打印:
```php if ( defined( 'ZSENS_DEBUG' ) && ZSENS_DEBUG ) { error_log( 'Option saved: ' . $option_name . ' | autoload: ' . $autoload ); error_log( 'Cache groups flushed: ' . implode( ', ', $flushed_groups ) ); } ```五、激活/卸载钩子:清理脚本别留雷
`register_activation_hook` 里建表、设默认配置;`register_uninstall_hook` 里删数据——但很多人忘了「停用」和「卸载」是两个钩子。用户点「停用」时保留数据是合理的,但要在文档里说清楚。更坑的是多站点激活:
```php register_activation_hook( __FILE__, 'zsens_activate' ); function zsens_activate( $network_wide ) { if ( $network_wide ) { foreach ( get_sites() as $site ) { switch_to_blog( $site->blog_id ); zsens_run_activation_logic(); restore_current_blog(); } } else { zsens_run_activation_logic(); } } ```上线前用 `wp site list` 拉个多站点环境跑一遍,单站点激活逻辑在 `switch_to_blog` 后可能全崩。
六、最后一步:「陌生人测试」
清空浏览器缓存,开个无痕窗口,用最低权限账号登录,把插件文档里写的每个功能点徒手点一遍。不要依赖自动化测试——它能验逻辑,但验不出「这个按钮出现在这里合不合理」的产品直觉。
我这份清单现在是个 Markdown 文件,每次发版前打勾,勾完才能打 tag。那次订阅者看见导出按钮的事故后,运营现在会随机抽账号做「越权抽查」,形成闭环了。
你们上线前有什么必查项?或者因为漏了哪项被半夜艾特过?欢迎补漏。