做了个付费内容站用户想看教程得先付费。自己写了一套卡密验证方案分享一下完整代码。一、使用方式在需要保护的页面顶部加两行代码phprequire_once __DIR__ . /keyt.cn.php; if (!cardAuth()) exit;用户访问该页面时自动弹出卡密验证框输入正确卡密才能看到内容。二、核心代码keyt.cn.php完整代码站长部署在网站根目录php?php /** * 卡密验证系统 * 在需要保护的页面顶部 require 此文件即可 */ // 配置替换成你自己的 define(API_URL, https://你的域名/check.php); define(APP_NAME, 你的应用名); define(SIGN_KEY, 你的签名密钥); // if (session_status() PHP_SESSION_NONE) { session_start(); } function cardAuth() { if (!isset($_SESSION[card_verified]) || $_SESSION[card_verified] ! true) { redirectToVerify(); return false; } if (isset($_SESSION[card]) !empty($_SESSION[card])) { $result heartbeatCheck($_SESSION[card]); if (!$result[valid]) { session_destroy(); session_start(); $_SESSION[error] $result[msg]; redirectToVerify(); return false; } $_SESSION[remain_days] $result[days]; $_SESSION[remain_minutes] $result[minutes]; } registerHeartbeatScript(); return true; } function redirectToVerify() { $redirect isset($_SERVER[REQUEST_URI]) ? $_SERVER[REQUEST_URI] : /; if (strpos($redirect, keyt.cn.php) false) { header(Location: /keyt.cn.php?redirect . urlencode($redirect)); } else { header(Location: /keyt.cn.php); } exit; } function registerHeartbeatScript() { register_shutdown_function(function() { $content ob_get_clean(); if ($content false) { echo script . getHeartbeatJS() . /script; return; } $js script . getHeartbeatJS() . /script; $pos strripos($content, /body); if ($pos ! false) { $content substr($content, 0, $pos) . $js . substr($content, $pos); } else { $content . $js; } echo $content; }); ob_start(); } function getHeartbeatJS() { return JS (function() { var CHECK_URL /keyt.cn.php?heartbeat1t Date.now(); var HEARTBEAT_INTERVAL 60000; function doHeartbeat() { fetch(CHECK_URL, { credentials: same-origin }) .then(function(r) { return r.text(); }) .then(function(data) { if (data.trim() EXPIRED) { alert(⚠️ 卡密已失效或过期请重新验证); window.location.href /keyt.cn.php?redirect encodeURIComponent(window.location.pathname); } }) .catch(function() {}); } setInterval(doHeartbeat, HEARTBEAT_INTERVAL); document.addEventListener(visibilitychange, function() { if (!document.hidden) doHeartbeat(); }); })(); JS; } function verifyCard($card) { $mac web_ . md5($_SERVER[REMOTE_ADDR] ?? 0.0.0.0); $url API_URL . ?card . urlencode($card) . mac . urlencode($mac) . app . APP_NAME; $ch curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $response curl_exec($ch); $error curl_error($ch); curl_close($ch); if ($error) { return [code error, msg 网络连接失败请稍后再试]; } $pos strpos($response, |sign); if ($pos ! false) { $response substr($response, 0, $pos); } if (strpos($response, system_locked) ! false) { return [code error, msg 系统维护中请稍后再试]; } $parts explode(|, $response); if ($parts[0] ok) { $result [code ok, msg 验证成功]; if (isset($parts[1]) $parts[1] permanent) { $result[days] 永久; $result[minutes] 999999; } elseif (isset($parts[2]) is_numeric($parts[2])) { $result[days] $parts[2]; $result[minutes] isset($parts[3]) ? intval($parts[3]) : intval($parts[2]) * 1440; } else { $result[days] 未知; $result[minutes] 0; } return $result; } else { $errorCode isset($parts[1]) ? $parts[1] : 未知错误; return [code error, msg translateError($errorCode)]; } } function heartbeatCheck($card) { $mac web_ . md5($_SERVER[REMOTE_ADDR] ?? 0.0.0.0); $url API_URL . ?card . urlencode($card) . mac . urlencode($mac) . app . APP_NAME; $ch curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 5); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $response curl_exec($ch); $error curl_error($ch); curl_close($ch); if ($error) { return [ valid true, msg 网络波动, days $_SESSION[remain_days] ?? 未知, minutes $_SESSION[remain_minutes] ?? 0 ]; } $pos strpos($response, |sign); if ($pos ! false) { $response substr($response, 0, $pos); } if (strpos($response, system_locked) ! false) { return [valid false, msg 系统已锁死请联系管理员]; } $parts explode(|, $response); if ($parts[0] ok) { $days 未知; $minutes 0; if (isset($parts[1]) $parts[1] permanent) { $days 永久; $minutes 999999; } elseif (isset($parts[2]) is_numeric($parts[2])) { $days $parts[2]; $minutes isset($parts[3]) ? intval($parts[3]) : intval($parts[2]) * 1440; } return [valid true, msg 有效, days $days, minutes $minutes]; } else { $errorCode isset($parts[1]) ? $parts[1] : 未知错误; return [valid false, msg translateError($errorCode)]; } } function translateError($code) { $map [ missing_params 参数不完整, app_not_found 应用不存在, invalid_card 卡密无效请检查是否输入正确, expired 卡密已过期, banned 卡密已被禁用, device_mismatch 设备不匹配, online_limit_reached 在线设备数已满, too_frequent 请求过于频繁请稍后再试, system_locked 系统已锁死请联系管理员, not_bound 卡密未绑定设备, unbind_disabled 该卡密不支持自助解绑, not_activated 卡密未激活, ]; return isset($map[$code]) ? $map[$code] : 验证失败 . $code; } // // 以下代码仅在直接访问 keyt.cn.php 时执行验证页面 // if (basename($_SERVER[PHP_SELF]) keyt.cn.php !isset($_SERVER[HTTP_X_REQUESTED_WITH])) { if (isset($_GET[heartbeat])) { header(Content-Type: text/plain); if (isset($_SESSION[card_verified]) $_SESSION[card_verified] true isset($_SESSION[card])) { $result heartbeatCheck($_SESSION[card]); echo $result[valid] ? OK : EXPIRED; } else { echo EXPIRED; } exit; } $error $_SESSION[error] ?? ; unset($_SESSION[error]); if ($_SERVER[REQUEST_METHOD] POST isset($_POST[card])) { $card trim($_POST[card]); $redirect isset($_POST[redirect]) ? $_POST[redirect] : /; if (empty($card)) { $error 请输入卡密; } else { $result verifyCard($card); if ($result[code] ok) { $_SESSION[card_verified] true; $_SESSION[card] $card; $_SESSION[verify_time] time(); $_SESSION[remain_days] $result[days] ?? 永久; $_SESSION[remain_minutes] $result[minutes] ?? 0; header(Location: . $redirect); exit; } else { $error $result[msg]; } } } $redirect isset($_GET[redirect]) ? $_GET[redirect] : (isset($_POST[redirect]) ? $_POST[redirect] : /); ? !DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title卡密验证/title style * { margin:0; padding:0; box-sizing:border-box; } body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; background: linear-gradient(145deg, #0f172a 0%, #1e293b 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 20px; } .verify-box { background: #ffffff; border-radius: 24px; padding: 48px 40px 40px; width: 440px; max-width: 100%; box-shadow: 0 30px 60px rgba(0,0,0,0.4); } .verify-box .icon { text-align:center; font-size:56px; margin-bottom:12px; } .verify-box h2 { text-align:center; font-size:24px; font-weight:700; color:#0f172a; margin-bottom:6px; } .verify-box .sub { text-align:center; font-size:14px; color:#94a3b8; margin-bottom:28px; } .verify-box .input-group { margin-bottom:18px; } .verify-box .input-group input { width:100%; padding:14px 18px; border:2px solid #e2e8f0; border-radius:12px; font-size:18px; outline:none; transition:border 0.2s; background:#f8fafc; letter-spacing:2px; font-family:monospace; text-align:center; } .verify-box .input-group input:focus { border-color:#4f46e5; background:#ffffff; } .verify-box .input-group .hint { font-size:12px; color:#94a3b8; margin-top:6px; text-align:center; } .verify-box .btn { width:100%; padding:14px; border:none; border-radius:12px; font-size:17px; font-weight:700; color:#fff; background:#4f46e5; cursor:pointer; transition:background 0.2s, transform 0.1s; } .verify-box .btn:hover { background:#4338ca; } .verify-box .btn:active { transform:scale(0.97); } .verify-box .btn:disabled { opacity:0.6; cursor:not-allowed; } .verify-box .error { background:#fef2f2; color:#dc2626; padding:10px 14px; border-radius:10px; font-size:14px; margin-bottom:16px; text-align:center; border:1px solid #fca5a5; } .verify-box .footer { text-align:center; font-size:12px; color:#94a3b8; margin-top:20px; padding-top:16px; border-top:1px solid #f1f5f9; } .verify-box .footer a { color:#4f46e5; text-decoration:none; } .loading { display:none; text-align:center; margin:12px 0; color:#94a3b8; font-size:14px; } .loading.show { display:block; } .spinner { display:inline-block; width:20px; height:20px; border:3px solid #e2e8f0; border-top-color:#4f46e5; border-radius:50%; animation:spin 0.6s linear infinite; vertical-align:middle; margin-right:8px; } keyframes spin { to { transform:rotate(360deg); } } /style /head body div classverify-box div classicon/div h2请输入卡密/h2 p classsub验证通过后即可正常访问/p ?php if (!empty($error)): ? div classerror❌ ?php echo htmlspecialchars($error); ?/div ?php endif; ? form methodpost idverifyForm input typehidden nameredirect value?php echo htmlspecialchars($redirect); ? div classinput-group input typetext idcard namecard placeholder请在此输入卡密 autofocus required div classhint 卡密区分大小写请准确输入/div /div div idloading classloading span classspinner/span 验证中请稍候... /div button typesubmit classbtn idsubmitBtn✅ 验证卡密/button /form div classfooter 遇到问题请联系 a href#管理员/a /div /div script document.getElementById(verifyForm).addEventListener(submit, function() { var btn document.getElementById(submitBtn); var loading document.getElementById(loading); var card document.getElementById(card).value.trim(); if (!card) { alert(请输入卡密); return false; } btn.disabled true; btn.textContent ⏳ 验证中...; loading.classList.add(show); return true; }); document.addEventListener(DOMContentLoaded, function() { document.getElementById(card).focus(); }); /script /body /html ?php exit; } ?三、部署说明将上述代码保存为keyt.cn.php上传到网站根目录在需要保护的页面顶部添加两行代码phprequire_once __DIR__ . /keyt.cn.php; if (!cardAuth()) exit;后台管理可生成、禁用卡密查看使用记录和在线统计四、完整源码完整项目源码含管理后台、API接口、多语言对接示例已开源可在卡密通官网https://www.keyt.cn下载。