本文约 4,338 字,预计阅读 11 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:权限一旦设计反了,改起来要动全站
一个付费社区通常有三种内容:完全公开的引流内容、登录可见的社区内容、按等级开放的付费内容。很多站长一开始只做了”公开 / 付费”两档,等要加”年度会员专属”时,才发现模板里到处是硬编码的 is_user_logged_in() 判断,改一处漏三处。等级的划分应该在建站第一天就按”内容粒度”倒推:你打算保护到文章级、分类级,还是某一段文字级?粒度决定了插件选型和后续维护成本。
原理分析:权限判断的三个层次
| 层次 | 粒度 | 实现位置 | 典型钩子 |
|---|---|---|---|
| 页面级 | 整页重定向 | template_redirect |
template_redirect |
| 内容级 | 单篇文章/分类 | the_content、pre_get_posts |
pre_get_posts |
| 片段级 | 段落/短代码 | 短代码回调 | add_shortcode |
主流插件里,Paid Memberships Pro(PMPro)免费版自带无限等级、内容保护与签到流程;MemberPress 规则引擎更强但为纯商业插件;Restrict Content Pro 轻量、代码干净,适合开发者。中文社区场景下,PMPro 加免费扩展是最实际的组合。
PMPro 的核心函数有三个:pmpro_hasMembershipLevel() 判断当前用户是否拥有某等级,pmpro_getMembershipLevelForUser() 取用户的等级对象,pmpro_getLevel() 取等级配置。二次开发基本围绕这三个函数展开。
保姆级实操
步骤 1:先定等级矩阵,再建等级。 把等级写成一张表:免费注册(看社区)、月度会员(看专栏)、年度会员(看专栏 + 资料库 + 答疑)。等级不要超过 4 个,等级越多用户决策成本越高。
步骤 2:安装并配置 PMPro。 在”Memberships → Settings”里设置”Membership Levels”页面、”Levels”页面与”Account”页面,插件安装向导会自动生成。支付网关先配测试模式跑通再切生产。
步骤 3:用短代码做片段级保护。 PMPro 自带 [membership level="1"]...[/membership] 短代码,适合保护文章中间的一段下载链接或答案。
// 自定义一个更贴合中文场景的片段保护短代码
add_shortcode( 'member_only', 'mimangfei_member_only_shortcode' );
function mimangfei_member_only_shortcode( $atts, $content = null ) {
$atts = shortcode_atts( array( 'level' => '1' ), $atts, 'member_only' );
$levels = array_filter( array_map( 'intval', explode( ',', $atts['level'] ) ) );
if ( ! is_user_logged_in() ) {
return '<div class="locked">该内容登录后可见,<a href="' . esc_url( wp_login_url( get_permalink() ) ) . '">点此登录</a></div>';
}
if ( function_exists( 'pmpro_hasMembershipLevel' ) && ! pmpro_hasMembershipLevel( $levels ) ) {
return '<div class="locked">该内容需升级会员后可见,<a href="' . esc_url( home_url( '/pricing/' ) ) . '">查看方案</a></div>';
}
return do_shortcode( $content );
}
步骤 4:做页面级保护。 在 template_redirect 阶段拦截,比在模板里判断更早、更省资源,也能避免正文被节选提前输出。
add_action( 'template_redirect', 'mimangfei_protect_single' );
function mimangfei_protect_single() {
if ( ! is_singular( array( 'post', 'page' ) ) ) {
return;
}
$required = (array) get_post_meta( get_queried_object_id(), '_required_level', true );
$required = array_filter( array_map( 'intval', (array) $required ) );
if ( empty( $required ) ) {
return;
}
if ( ! is_user_logged_in() || ! pmpro_hasMembershipLevel( $required ) ) {
wp_safe_redirect( home_url( '/pricing/?from=' . get_queried_object_id() ) );
exit;
}
}
步骤 5:把受保护内容从列表里剔除。 否则首页摘要会泄露付费内容的第一段。用 pre_get_posts 加 meta_query 排除带 _required_level 的文章。
add_action( 'pre_get_posts', 'mimangfei_hide_member_posts' );
function mimangfei_hide_member_posts( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
if ( $query->is_home() || $query->is_archive() || $query->is_search() ) {
if ( ! is_user_logged_in() || ! pmpro_hasMembershipLevel( array( 1, 2, 3 ) ) ) {
$query->set( 'meta_query', array(
array(
'key' => '_required_level',
'compare' => 'NOT EXISTS',
),
) );
}
}
}
步骤 6:保护上传文件。 这是最容易被忽略的一环:文章拦住了,PDF 直链还在公网上。用 Nginx 给 wp-content/uploads/protected/ 加 internal 规则,由 PHP 鉴权后通过 X-Accel-Redirect 返回。
location ^~ /wp-content/uploads/protected/ {
internal;
alias /var/www/html/wp-content/uploads/protected/;
}
// 鉴权通过后由 Nginx 内部重定向输出文件,直链无法直接访问
function mimangfei_send_protected_file( $file_rel_path ) {
if ( ! pmpro_hasMembershipLevel( array( 2, 3 ) ) ) {
wp_die( '无权访问该文件' );
}
header( 'X-Accel-Redirect: /wp-content/uploads/protected/' . ltrim( $file_rel_path, '/' ) );
header( 'Content-Type: application/pdf' );
exit;
}
步骤 7:配置到期与续费提醒。 在 PMPro 的”Settings → Email”里开启到期前提醒,并接入 SMTP 保证送达。
常见踩坑
- 等级变更后缓存未刷新。 用户刚付款 upgrade,页面仍显示旧权限,需在等级变更钩子里清缓存。
- 首页摘要泄露付费内容。 只做了
the_content判断,没处理pre_get_posts。 - PDF 直链外泄。 上传目录没有做鉴权,搜索引擎甚至能索引到内部资料。
- 案例: 某会员站在模板里用
is_user_logged_in()判断权限,结果所有注册用户(包括免费注册)都能看到年度会员专区,持续两个月才发现。根因是”登录”与”付费”被混为一谈,必须判断等级而不是登录态。 - 未登录跳转导致收录问题。 给爬虫返回 200 的正常内容,只对真人用户跳转。
- 短代码嵌套没用
do_shortcode()。 会员内容里的其他短代码不解析。 - 测试网关切生产时忘了换密钥。 支付一直走沙箱,订单不生效。
总结
会员站的三条主线是等级、权限、文件。判断方案是否合格的三个标准:未付费用户在服务端就拿不到正文、列表页不泄露付费摘要、上传目录的直链被 Web 服务器拦住。等级控制在 4 个以内,权限判断集中在钩子里而不是散落模板。会员到期后怎么自动降级,见会员续费与权限回收;社区互动部分怎么接,见WordPress 论坛社区搭建。
