简介:由博客 dsdiss.com 开发的一款名为 Dsdiss Verification 的 WordPress 插件,核心用途是隐藏文章部分内容,访客关注公众号并获取密码后方可查看,从而帮助内容型网站站长完成微信公众号引流。后台新增“二维码设置”页面,可配置验证密码、提示文字与二维码图片;通过 [protected] 短代码在文章中标注受保护片段,验证成功后会通过 cookie 记录状态,使用门槛低,非技术型运营者也能借助后台完成配置。压缩包为 zip 格式,整体仅 4KB,共 3 个文件:PHP 主文件负责后台设置与短代码解析,JS 脚本处理前端验证交互,CSS 文件控制隐藏区域样式。插件包虽小,但完整覆盖了从文章内容保护、用户输入密码、写入 cookie 到显示隐藏内容的闭环流程,也便于开发者理解 WordPress 插件开发中的设置 API、短代码注册、静态资源引入及验证状态管理等常见知识点。目前已有 48 人学习下载,适合微信公众号运营者、WordPress 站长以及希望学习内容隐藏类插件实现的初级开发者参考。
1. 不只是隐藏:微信公众号导流插件的验证机制与场景
如果一个 WordPress 站点只是把文章截断,用户根本不会去扫码关注公众号。真正让人愿意掏出手机扫码的,是那种“内容就在眼前但被锁住”的微妙心理。Dsdiss Verification 正是这类插件里非常轻量的一款,它的核心逻辑只有一个短代码[protected]:把文章某段内容包起来,访客从公众号那边拿到密码后输入验证,通过验证就靠 Cookie 记住状态,刷新后内容直接可见。它不引入会员体系,不校验微信 openid,也不改文章表结构,对现有站点几乎没有侵入性。适合放课程资料、下载链接、活动代码或者临时需要访问门槛的文章,也适合运营人员用来拉公众号的关注量。由于整个流程只有“密码 + Cookie + 短代码”三件事,排错和二次开发都容易。
2. 拆解 dsdiss-verification.php:设置项注册与短代码定义
把压缩包解压后,核心内容就三个文件:dsdiss-verification.php是主入口,style.css控制锁定框样式,script.js负责前端交互。按 WordPress 插件惯例,整个目录复制到/wp-content/plugins/dsdiss-verification/即可被识别。主文件开头通常有一份插件头注释,用来声明插件的名称和启用入口。
/* Plugin Name: Dsdiss Verification Description: 隐藏文章部分内容,验证密码后通过 Cookie 解锁 Version: 1.0 */插件头注释是 WordPress 识别插件的基础,没有它后台插件列表会直接报“文件内容错误”。下面的资源加载和设置项注册都依赖这个主文件正常载入。理解了这个入口,后面看短代码和 AJAX 逻辑才不会乱。
2.1 插件文件结构与加载入口
| 文件 | 作用 |
|---|---|
| dsdiss-verification.php | 插件主入口,负责短代码注册、后台菜单、设置项和 AJAX 回调 |
| style.css | 锁定提示框、二维码图片、表单的默认样式 |
| script.js | 表单提交、AJAX 验证、写 Cookie、刷新页面 |
资源加载是第一个关键点。原文插件会在前台页面同时引入 CSS 和 JS,并提前准备好 AJAX 地址。常见做法是这样:
function dsdiss_enqueue_assets() { wp_enqueue_style('dsdiss-verification', plugin_dir_url(__FILE__) . 'style.css', array(), '1.0'); wp_enqueue_script('dsdiss-verification', plugin_dir_url(__FILE__) . 'script.js', array('jquery'), '1.0', true); wp_localize_script('dsdiss-verification', 'dsdiss_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), )); } add_action('wp_enqueue_scripts', 'dsdiss_enqueue_assets');这里用plugin_dir_url(__FILE__)而不是硬编码路径,是因为插件目录可能被重命名,硬编码在迁移时第一个出问题。wp_localize_script把 AJAX 地址暴露给 JS 里的dsdiss_ajax.ajax_url,避免在脚本里写死域名。加载顺序上,JS 放到页脚位置,不阻塞文章页首屏渲染。
提示:如果前台样式没有生效,优先在浏览器开发者工具里看
style.css是否返回 404,再检查插件目录名是否和代码拼出来的路径一致。
2.2 后台菜单与设置项注册
后台菜单的作用是让管理员能设置验证密码、提示内容和二维码图片。原插件在 WordPress 后台添加了一个“二维码设置”菜单,对应的结构可以还原为:
add_action('admin_menu', function () { add_menu_page( '二维码设置', '二维码设置', 'manage_options', 'dsdiss-verification', 'dsdiss_render_options_page' ); }); add_action('admin_init', function () { register_setting('dsdiss_settings_group', 'dsdiss_password'); register_setting('dsdiss_settings_group', 'dsdiss_tip_text'); register_setting('dsdiss_settings_group', 'dsdiss_qrcode_url'); });add_menu_page的参数分别是页面标题、菜单标题、用户权限、菜单别名、回调函数。这里使用manage_options限制为管理员权限,避免订阅者进入后台设置页。register_setting的第一个参数是设置组名,必须和后面保存表单里settings_fields填写的组名完全一致,否则 WordPress 会拒绝保存并提示“操作不合法”。
从存储设计看,三个值各自存成独立 option,而不是合成一个数组,是为了读取时直接用get_option拿字符串,省去解析数组的步骤。缺点是设置项变多后会占更多wp_options表记录,单站点问题不大,多站点则需要考虑聚合存储。
2.3 短代码 [protected] 的解析与权限判断
短代码是整个插件的核心。文章编辑器里写了[protected]...[/protected],WordPress 解析时会调用注册的回调函数。原插件的判断逻辑很直白:读取全局密码,检查 Cookie 中的值与密码指纹是否一致,一致就输出隐藏内容,否则渲染锁定提示和表单。
add_shortcode('protected', 'dsdiss_protected_render'); function dsdiss_protected_render($atts, $content = null) { $password = get_option('dsdiss_password', ''); $verified = isset($_COOKIE['dsdiss_verified']) && $_COOKIE['dsdiss_verified'] === md5($password); if ($verified) { return do_shortcode($content); } $tip = get_option('dsdiss_tip_text', '请关注公众号后获取密码'); return sprintf( '<div class="dsdiss-locked">%s<form id="dsdiss-verify-form"><input type="password" name="dsdiss_password" /><button type="submit">验证</button></form></div>', esc_html($tip) ); }这里用md5($password)做 Cookie 标记,只是防止明文密码直接出现在前端,严格来说它不是安全方案。因为 md5 值等于密码指纹,抓到 Cookie 后可以用彩虹表反查。生产环境我更倾向于用wp_hash或hash_hmac('sha256', $password, wp_salt())生成标记。另外$content必须经过do_shortcode,否则隐藏内容里嵌套的其他短代码不会解析。
注意:
$atts参数在当前版本没有实际参与判断,但它为后续扩展留下了空间,比如给短代码加id属性实现不同文章不同密码。这一点在后文会展开。
3. 前端验证链路:从密码框到 Cookie 标记
后台逻辑只负责判断“当前请求是否已通过验证”,真正的交互发生在script.js。用户点击验证按钮后,JS 把密码提交到 admin-ajax.php,PHP 返回成功标记,JS 把标记写入 Cookie,最后刷新页面让短代码重新渲染。整个过程不需要跳转,体验比传统表单提交更顺畅。
3.1 admin-ajax 接口与密码校验
WordPress 插件最常用的 AJAX 入口是admin-ajax.php。Dsdiss Verification 注册了wp_ajax_和wp_ajax_nopriv_两个钩子,因为文章访客大多没有注册账号,必须允许匿名请求。
add_action('wp_ajax_dsdiss_verify', 'dsdiss_handle_verify'); add_action('wp_ajax_nopriv_dsdiss_verify', 'dsdiss_handle_verify'); function dsdiss_handle_verify() { $input = isset($_POST['password']) ? wp_unslash($_POST['password']) : ''; $pass = get_option('dsdiss_password', ''); if ($input === $pass) { wp_send_json_success(array( 'token' => md5($pass) )); } wp_send_json_error(array('message' => '密码错误,请重新核对公众号里的提示。'), 403); }wp_send_json_success会自动输出 JSON 并结束请求,比手动拼接 JSON 再wp_die省事。wp_unslash是必须的,因为 WordPress 会默认给$_POST加 slashes,如果不处理,和 option 里保存的密码比对时大概率失败。从安全角度,这个接口缺少 nonce 校验,意味着理论上有爆破风险。自己部署时建议在表单里加wp_nonce_field,然后用check_ajax_referer校验。
3.2 script.js 的状态写入与刷新策略
script.js 的任务是拦截表单提交、发起 AJAX、写 Cookie、刷新页面。最直接的做法是:
jQuery(function($) { $(document).on('submit', '#dsdiss-verify-form', function(e) { e.preventDefault(); var pwd = $(this).find('input[name="dsdiss_password"]').val(); $.post(dsdiss_ajax.ajax_url, { action: 'dsdiss_verify', password: pwd }).done(function(res) { if (res.success) { document.cookie = 'dsdiss_verified=' + res.data.token + '; path=/; max-age=3600; SameSite=Lax'; window.location.reload(); } else { alert(res.data.message); } }).fail(function() { alert('验证请求失败,请检查网络后重试。'); }); }); });max-age=3600表示 Cookie 有效期 1 小时,单位是秒;如果公众号固定每天更新口令,可以调整到 86400。path=/让 Cookie 在整个站点内生效,这样用户从文章甲跳到文章乙,只要两篇文章共用同一个密码,就不会重复验证。子目录安装站点要注意路径设置,否则会出现“这个页面验证成功,另一个页面又锁住”的问题。
这里不设置HttpOnly是因为 JS 需要显式写入 Cookie。代价是如果站点存在 XSS 漏洞,攻击者也能读到这个验证标记。更稳妥的方案是让 PHP 端校验后直接setcookie,再跳回当前页,但那样交互会多一次跳转。原插件选择的是 JS 写 Cookie,胜在简洁。
3.3 Cookie 参数与常见认知误区
| Cookie 属性 | 参数值 | 影响 |
|---|---|---|
| name | dsdiss_verified | 对应 PHP 端读取$_COOKIE['dsdiss_verified'] |
| value | md5(password) | 避免明文密码进入前端,但不能防止重放 |
| path | / | 全站生效;子目录站点应使用 COOKIEPATH |
| max-age | 3600 | 过期时间;太长导致验证状态长期有效 |
| SameSite | Lax | 阻止第三方站携带该 Cookie 发出跨站请求 |
一个常被忽略的点:刷新页面后,短代码回调里只比较$_COOKIE['dsdiss_verified'] === md5($password),并没有校验这个 Cookie 是否来自当前站点。浏览器本身会按域名隔离 Cookie,所以跨站点伪造风险不高,但同网段用户如果抓包拿到 Cookie 值,在自己的浏览器里伪造同样值也能生效。这就是密码不能设置太简单的原因,最好混合字母和数字。
4. 二维码设置与后台参数调优
设置页看起来只有三个字段,但每个字段都直接决定用户是否会扫码。很多部署后转化率低的案例,问题都出在提示文本太含糊、二维码加载失败或者表单和二维码不在同一屏。
4.1 三个核心字段的作用与取值建议
后台“二维码设置”页需要维护以下三个配置项:
| 设置项标识 | 显示名称 | 说明 |
|---|---|---|
| dsdiss_password | 验证密码 | 公众号自动回复或图文消息中给出的口令 |
| dsdiss_tip_text | 提示内容 | 锁定框中的引导文字,需要明确写出获取方式 |
| dsdiss_qrcode_url | 二维码图片 URL | 公众号二维码图片的完整 URL,建议使用 HTTPS 绝对地址 |
密码建议用类似2025-x7k9这样的短口令,不要用超长随机字符串,用户从公众号复制到网页的过程很容易出错。提示文字要写清楚步骤,例如“打开微信扫一扫右侧二维码,关注后回复 dsdiss 获取访问密码”。二维码图片 URL 必须填完整地址,不能写相对路径,否则在部分浏览器和 App 内打开时图片会裂。
锁定框里需要通过短代码渲染二维码。常见做法是在返回表单前把二维码 URL 拼进提示区域:
$qr = get_option('dsdiss_qrcode_url', ''); if ($qr) { $tip .= sprintf( '<img src="%s" class="dsdiss-qrcode" alt="公众号二维码" />', esc_url($qr) ); }这样二维码和表单就出现在同一个.dsdiss-locked容器里。样式方面,style.css里通常会把容器改成 flex 布局,让文字、二维码、验证按钮在桌面端并排,在移动端自动折行。二维码图片建议限制max-width: 180px,避免撑破文章内容宽度。
4.2 后台表单渲染与保存逻辑
设置表单的标准写法是:
function dsdiss_render_options_page() { ?> <div class="wrap"> <h1>二维码设置</h1> <form method="post" action="options.php"> <?php settings_fields('dsdiss_settings_group'); ?> <table class="form-table"> <tr><th>验证密码</th><td><input name="dsdiss_password" value="<?php echo esc_attr(get_option('dsdiss_password')); ?>" /></td></tr> <tr><th>提示内容</th><td><textarea name="dsdiss_tip_text"><?php echo esc_textarea(get_option('dsdiss_tip_text')); ?></textarea></td></tr> <tr><th>二维码图片 URL</th><td><input name="dsdiss_qrcode_url" value="<?php echo esc_attr(get_option('dsdiss_qrcode_url')); ?>" /></td></tr> </table> <?php submit_button(); ?> </form> </div> <?php }表单的action指向options.php,由 WordPress 后台自动处理保存逻辑。settings_fields输出 nonce 和option_page隐藏字段,名称必须与register_setting的组名一致。填写字段时注意使用esc_attr和esc_textarea,防止后台设置页被恶意注入脚本。
如果后面想支持多密码,可以把dsdiss_password改成 JSON 字符串存储,但读取时要json_decode,并在短代码回调里根据$atts['id']选择对应密码。未指定id时最好回落到默认密码,保证旧文章不用改。
4.3 部署中的常见坑与排查方向
第一个最容易遇到的坑是缓存。站点启用页面缓存后,用户第一次看到锁定状态时页面被缓存,验证成功刷新后仍然返回缓存版本,Cookie 生效不了。处理办法是让缓存插件排除带dsdiss_verifiedCookie 的请求,或者干脆关闭含有[protected]页面的缓存。轻量站点可以直接禁用该文章页缓存。
第二个坑是二维码加载失败。如果后台填的是/wp-content/uploads/qr.png这样的相对路径,在前台网页上也许能访问,但在微信内置浏览器里可能因为基础路径变化被解析成错误地址。见到这种情况应改成https://your-domain.com/wp-content/uploads/qr.png的绝对地址,并避免使用短链接。
第三个坑是the_content过滤顺序。有的主题在输出文章内容时会对包含短代码的文本做wp_kses或其他转义,导致返回的 HTML 被吞掉。定位办法:先禁用所有其他插件,只保留本插件,如果锁定框正常显示,再逐个开启其他插件找到冲突源。
5. 扩展:让 [protected] 支持多篇文章不同密码
很多运营者做到第二个活动就会遇到问题:一篇活动文章一个密码,才能区分不同渠道的关注效果。原插件只支持一个全局密码,但短代码的属性参数本身就适合做这个扩展。比如把[protected id="course-a"]解析成读取dsdiss_pass_course-a,未指定id时回落到全局密码。
5.1 多密码短代码的实现
扩展后的短代码回调可以写成这样:
add_shortcode('protected', function ($atts, $content = null) { $atts = shortcode_atts(array('id' => 'default'), $atts, 'protected'); $password = 'default' === $atts['id'] ? get_option('dsdiss_password', '') : get_option('dsdiss_pass_' . $atts['id'], ''); $verified = isset($_COOKIE['dsdiss_verified_' . $atts['id']]) && $_COOKIE['dsdiss_verified_' . $atts['id']] === md5($password); if ($verified) { return do_shortcode($content); } $tip = get_option('dsdiss_tip_text', '请关注公众号获取密码'); return '<div class="dsdiss-locked">' . esc_html($tip) . '</div>'; });这里的关键是 Cookie 名也带上了id,避免多篇文章密码互相覆盖。如果忽略这一点,用户验证完 A 文章后,B 文章也会被误判为已解锁。后台的接口回调同样要接收一个shortcode_id字段,返回对应密码的 token,否则前端的 Cookie 永远写不进去。
5.2 验证方法与样式细节
部署后可以用浏览器开发者工具验证:打开“应用”面板,查看dsdiss_verified_course-a的 value 是否等于对应密码的 md5 值。确认一致后刷新页面,如果该篇文章的隐藏内容可见,而另一篇使用不同id的文章仍然锁定,说明隔离逻辑正确。
最后一个细节:锁定框里的二维码图片建议在style.css里设置max-width: 180px; border-radius: 8px;,并让图片与提示文字垂直居中。小屏手机用户扫码时,尺寸过大的二维码会被截断,影响整体转化率。
本文还有配套的精品资源,点击获取