防红接口实现全解析:UA识别与跳转网关核心逻辑

发布时间:2026/9/12 2:30:44
防红接口实现全解析:UA识别与跳转网关核心逻辑 简介一份面向网站运营者与推广人员的防红接口源码基于PHP开发支持对已红域名在微信/QQ内直接打开并集成直连、跳转、短链接生成及防红界面。系统自带访问记录可查看访问者IP和访问方式前台支持用户注册、升级权限、购买套餐与生成独立KEY后台可管理套餐并对接易支付普通用户或会员可自定义接口域名。包体共369个文件约4.1MB以86个PHP业务逻辑文件、82个JS交互脚本、36个CSS样式为主另有HTML模板、SQL数据库文件及少量字体图片资源结构清晰便于二次开发。当前已有208人学习下载。资源为v2.2版本包含完整源码及更新记录重点处理了注册邮件发送、支付回调、工单回复、域名重复生成等问题并加入动态次数效果、用户独立生成统计、续费自动更换域名和彩虹代码方便快速部署一套带会员体系的独立防红系统。1. 我理解的“防红接口”要解决什么问题一条链接放进微信或 QQ 的聊天窗口后平台会先派爬虫抓一次页面。如果域名、路径或关键词触发规则真实用户再打开就是“已停止访问该网页”。所谓防红接口就是在爬虫抓取时给一套干净页面在真实点击时再放行到目标地址。它本质是双分支跳转网关同一个接口地址按访问方身份返回不同内容。这套系统真正难的不是跳转而是访问源识别。常见的做法是用 UA 和 Referer 区分微信、QQ、普通浏览器再分别走中间提示页和直连跳转。下文直接拆解表结构、跳转接口、易支付对接和上线调参目标是让你在服务器上搭出最小可用版本。2. 防红系统的任务表与最小表结构2.1 三种访问源三套业务分支防红接口收到请求后先不急着跳转而是先回答三个问题这个请求是从哪个 App 的内置浏览器发出的这个请求对应的原始链接是什么我要不要给这个来源直连权限针对这三个问题系统一般会分出三个分支。微信内置浏览器UA 里带MicroMessenger走中间提示页让用户复制链接到外部浏览器QQ 内置浏览器UA 里带Mobile QQ或QQBrowser走独立中间页或直接跳转普通浏览器比如 Chrome、Safari直接返回公开提示页。为什么三个分支不能返回同样内容因为平台爬虫在抓取时使用的 UA 大概率是普通浏览器 UA。如果你把普通浏览器的行为也设置成直接跳转爬虫就会跟着到目标站目标域名和页面内容一旦被抓取识别问题就在源头被定位。所以普通浏览器分支返回公开页面或 404是整个系统最重要的安全边界。2.2 字段设计一个跳转任务需要哪些字段防红系统的核心表是一张跳转任务表每条记录代表一个对外可访问的短链。我一般会这样设计字段字段类型说明idint unsigned主键task_keyvarchar(32)对外短链码例如 aB3xYtarget_urlvarchar(255)真实落地地址仅对放行来源开放check_uatinyint(1)是否启用 UA 识别direct_uavarchar(255)允许直连的 UA 关键词多个用逗号分隔allow_sharetinyint(1)是否允许被转发expire_timedatetime链接过期时间statustinyint(1)0 停用1 启用create_timedatetime创建时间关键字段是direct_ua。它存的是可以放行的 UA 关键词集合而不是完整 UA。完整 UA 会带版本号今天允许的版本明天就被平台换掉用关键词匹配比完整匹配稳定得多。比如MicroMessenger这个字符在微信 6.x 到 8.x 的 UA 里一直稳定存在。2.3 用 SQL 初始化一张可用的防红任务表CREATE TABLE anti_red_task ( id int unsigned NOT NULL AUTO_INCREMENT, task_key varchar(32) NOT NULL COMMENT 对外短链码, target_url varchar(255) NOT NULL COMMENT 真实落地地址, check_ua tinyint(1) NOT NULL DEFAULT 1 COMMENT 是否启用UA识别, direct_ua varchar(255) NOT NULL DEFAULT MicroMessenger,QQ COMMENT 允许直连的UA关键词, allow_share tinyint(1) NOT NULL DEFAULT 0 COMMENT 是否允许被转发, expire_time datetime DEFAULT NULL COMMENT 过期时间, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 0停用 1启用, create_time datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_task_key (task_key), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT防红跳转任务表;task_key要建唯一索引因为对外短链码需要在接口里被快速查询status建普通索引用于后台管理页筛选启停记录。expire_time可以先不建索引等数据量大到需要按期清理时再补。表里direct_ua默认值写了MicroMessenger,QQ这是针对微信和 QQ 内置浏览器的两个最常用关键词。实际生产不要只依赖默认值平台对 UA 的调整策略经常变化第 5 章会专门讲这个参数怎么调。3. 直连防红接口的核心代码识别来源与下发目标地址3.1 UA 识别微信、QQ、普通浏览器怎么区分先明确一点UA 不是可信的防红系统不会把 UA 当成唯一信任来源它是第一层筛选。在 PHP 里$_SERVER[HTTP_USER_AGENT]能拿到当前请求的 UA 字符串。微信内置浏览器的 UA 里几乎都有MicroMessengerQQ 内置浏览器常见的标识是Mobile QQ或QQBrowser另外经典特征是Android、iPhone这类操作系统标识出现在靠前的位置。把判断逻辑封装成函数?php function checkSourceEnv(string $ua): string { $ua strtolower($ua); if (strpos($ua, micromessenger) ! false) { return wechat; } if (strpos($ua, mobile qq) ! false || strpos($ua, qqbrowser) ! false) { return qq; } return normal; }逻辑很简洁UA 转小写后分别查找micromessenger和mobile qq、qqbrowser返回值对应微信、QQ 和普通浏览器。注意不要写成if (strpos(...))因为目标关键词出现在 UA 开头时strpos返回0而0是假值会漏判。必须用! false判断。3.2 直连跳转接口的完整实现拿到来源环境后下一步决定怎么处理这次请求。下面这段代码是一个最小可用的直连防红跳转接口放在jump.php?php // 防红直连跳转接口入口参数?ktask_key $taskKey $_GET[k] ?? ; if ($taskKey ) { http_response_code(400); exit(invalid request); } // 从数据库读取任务记录 $pdo new PDO(mysql:host127.0.0.1;dbnameanti_red, root, password); $stmt $pdo-prepare(SELECT * FROM anti_red_task WHERE task_key ? AND status 1 LIMIT 1); $stmt-execute([$taskKey]); $task $stmt-fetch(PDO::FETCH_ASSOC); if (!$task) { http_response_code(404); exit(task not found); } if ($task[expire_time] strtotime($task[expire_time]) time()) { http_response_code(410); exit(task expired); } $ua $_SERVER[HTTP_USER_AGENT] ?? ; $sourceEnv checkSourceEnv($ua); // 普通浏览器返回干净的提示页不要直接 302 if ($sourceEnv normal) { header(Content-Type: text/html; charsetutf-8); exit(htmlheadtitle提示/title/headbodyp请在微信或QQ中打开/p/body/html); } // 允许直连的 UA 关键词校验 $directUaList array_filter(array_map(trim, explode(,, $task[direct_ua]))); $allowed false; foreach ($directUaList as $keyword) { if (stripos($ua, $keyword) ! false) { $allowed true; break; } } if (!$allowed) { file_put_contents( __DIR__ . /logs/block.log, date(Y-m-d H:i:s) . . $ua . PHP_EOL, FILE_APPEND ); http_response_code(403); exit(forbidden); } // 放行302 跳转到真实落地地址 header(Location: . $task[target_url], true, 302); exit();代码分四步校验参数、查询任务并检查过期、判断来源环境、按来源分支决定提示或跳转。直连的意义在这里体现判定为普通浏览器的请求一律不走目标地址只返回静态提示页只有 UA 命中direct_ua关键词的来源才走 302。302 对用户无感知但后续链路仍会被目标站日志记录所以安全边界不在跳转本身而在跳转之前的判断。3.3 白名单校验与日志记录UA 伪造成本很低所以需要在跳转前加第二道校验Referer 白名单。?php $referer $_SERVER[HTTP_REFERER] ?? ; $allowedReferers [qzone.qq.com, wx.qq.com, mp.weixin.qq.com]; $refHost strtolower(parse_url($referer, PHP_URL_HOST) ?? ); if (!in_array($refHost, $allowedReferers, true)) { file_put_contents( __DIR__ . /logs/referer_block.log, date(Y-m-d H:i:s) . . $referer . PHP_EOL, FILE_APPEND ); http_response_code(403); exit(referer forbidden); }这段代码校验来路域名只放行三个白名单域名。注意parse_url对没有传Referer的请求返回null所以要先做空值合并用?? 兜底。微信内通过聊天窗口打开时Referer大概率是https://wx.qq.com/或https://mp.weixin.qq.com/但从扫码或小程序进入时部分场景Referer为空。所以 Referer 校验建议做成可配置项默认开启允许按任务维度关闭否则会误伤扫码流量。4. 对接易支付签名、下单与回调的完整链路4.1 易支付接口的参数约定易支付是一套第三方聚合支付协议市面上很多支付发包都按同一个参数规范实现。一个标准的易支付下单接口需要提交这些参数参数名是否必填说明pid是商户IDtype是支付方式如 alipay、wxpayout_trade_no是商户侧订单号notify_url是异步回调地址return_url是同步跳转地址name是商品名称money是金额单位元保留两位小数sign是签名sign_type是MD5参数多但核心只有两个签名算法和回调验签。易支付的标准签名规则是把所有参数按参数名 ASCII 从小到大排序拼接成key1value1key2value2末尾拼接商户密钥做 MD5得到 32 位字符串作为sign字段值。4.2 生成签名并发起下单请求下面代码展示如何生成签名并请求支付下单接口?php function sign(array $params, string $key): string { ksort($params); $str ; foreach ($params as $k $v) { if ($v || $k sign || $k sign_type) { continue; } $str . $k . . $v . ; } $str trim($str, ) . $key; return md5($str); } $params [ pid 1000, type alipay, out_trade_no 202501010001, notify_url https://jump.example.com/pay/notify.php, return_url https://jump.example.com/pay/return.php, name 会员开通, money 9.90, sign_type MD5, ]; $key your_mch_key; $params[sign] sign($params, $key); $ch curl_init(https://pay.example.com/submit.php); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $resp curl_exec($ch); curl_close($ch); echo $resp;签名的关键在于ksort。排序后把参与签名的参数用拼接末尾拼商户密钥再做 MD5。需要注意三点空值参数不参与签名sign和sign_type本身不参与签名http_build_query会做 URL 编码但签名拼接使用原始值所以参数值不要传包含特殊字符的内容否则签名对不上。4.3 支付回调验签与防重处理支付完成后平台向notify_url异步发送通知。回调里比下单参数多了trade_no和trade_status两个字段。验签逻辑和下单一样把收到的参数重新签名与sign对比通过后再判断trade_status。?php function verifyNotify(array $data, string $key): array { if (empty($data[sign])) { return [code 0, msg no sign]; } $params $data; $sign $params[sign]; unset($params[sign], $params[sign_type]); $calSign sign($params, $key); if ($calSign ! $sign) { return [code 0, msg sign mismatch]; } return [code 1, msg ok]; } $data $_POST; $result verifyNotify($data, $key); if ($result[code] ! 1) { exit(fail); } $orderId $data[out_trade_no] ?? ; $tradeStatus $data[trade_status] ?? ; if ($tradeStatus TRADE_SUCCESS) { // 防重只有 status0 的未处理订单才会被更新 $stmt $pdo-prepare(UPDATE pay_orders SET status 1 WHERE out_trade_no ? AND status 0); $stmt-execute([$orderId]); } // 必须输出纯文本 success否则平台会持续重推 exit(success);回调处理有两个必须注意的细节。一是防重入UPDATE ... WHERE status 0保证同一订单只有第一次回调能成功更新重复回调不会产生副作用。二是返回内容易支付要求回调接口输出纯文本success不能输出 JSON、HTML 或其他字符否则平台认为回调失败并继续重试。5. 直连防红系统的缓存与排错三个必调参数5.1 为什么要在防红接口前加 Redis 缓存防红接口的高频路径是查询任务表数据库按task_key做索引查找。当一个短链被分发到几百个群后同一秒可能涌进上千个请求数据库压力会直线上升。常见做法是用 Redis 缓存任务记录以anti_red:task:{key}为键存 JSON 序列化数据。?php $redis new Redis(); $redis-connect(127.0.0.1, 6379); $cacheKey anti_red:task: . $taskKey; $taskJson $redis-get($cacheKey); if ($taskJson ! false) { $task json_decode($taskJson, true); } else { $stmt $pdo-prepare(SELECT * FROM anti_red_task WHERE task_key ? AND status 1 LIMIT 1); $stmt-execute([$taskKey]); $task $stmt-fetch(PDO::FETCH_ASSOC); if ($task) { $redis-setex($cacheKey, 300, json_encode($task, JSON_UNESCAPED_UNICODE)); } }缓存超时 300 秒。太短起不到保护作用太长会导致后台改了状态前台不生效。更稳妥的做法是后台更新任务时主动DEL anti_red:task:{key}让缓存立刻失效。另外注意如果任务被停用SELECT查不到新记录旧缓存仍然会被持续读取直到超时所以停用操作的即时性要依赖主动删缓存而不是缓存自然过期。5.2 上线后必须调整的三个参数防红系统上线后我遇到最多需要调整的地方集中在这三个参数参数默认值调整场景direct_ua关键词集合MicroMessenger,QQ平台更新 UA 规则时旧关键词可能不再出现Redis 缓存超时300 秒活动运营期间调短平时调长Referer 白名单wx.qq.com 等扫码进入时 Referer 为空需要增加放行规则direct_ua是防红效果的直接决定项。微信和 QQ 更新内核后UA 字符串可能保留旧关键词也可能完全替换所以要定期从真实用户日志里提取新出现的特征更新到这个集合。不要只看网上别人分享的列表自己服务器日志里的 UA 才是第一手数据。5.3 两个日志点拦截日志与回调日志固定观察两个日志文件block.log记录被拦截的普通浏览器访问pay_callback.log记录支付回调原始数据和结果。拦截日志常见问题是“该跳的没跳”比如用户明明用微信打开却命中普通浏览器分支。这通常不是 UA 识别写错而是反向代理改写了HTTP_USER_AGENT。排查时先看 Nginx 配置里有没有proxy_set_header User-Agent如果用了框架再看中间件是否统一清掉了 UA。回调日志常见问题是“验签断言失败”。看到这类报错直接打印$_POST原始数组与支付平台文档对照。很多私有化部署的易支付网关会在回调字段前加自定义前缀或者字段名多一层嵌套。在回调接口第一行记录原始日志能让这类问题在几分钟内定位?php file_put_contents( __DIR__ . /logs/pay_callback.log, date(Y-m-d H:i:s) . . json_encode($_POST) . PHP_EOL, FILE_APPEND );记录日志时不要记录银行卡号、支付凭证等敏感字段日志文件权限建议设置为 640并定期做切割。6. 部署后用命令行模拟来源请求做防红直连自检6.1 用 curl 模拟微信、普通浏览器和扫码场景防红接口写完不能只靠真机点来验证真机实测效率低。我习惯把验证脚本化在命令行伪造不同 UA 和 Referer观察响应状态码与Location字段。# 模拟微信内置浏览器访问 curl -I -X GET https://jump.example.com/jump.php?kaB3xY \ -H User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.49 \ -H Referer: https://wx.qq.com/ # 模拟普通浏览器访问 curl -I -X GET https://jump.example.com/jump.php?kaB3xY \ -H User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/125.0 Safari/537.36 # 模拟不带 Referer 的扫码场景访问 curl -I -X GET https://jump.example.com/jump.php?kaB3xY \ -H User-Agent: Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 MicroMessenger/7.0.20用-I只取响应头。判断点有两个一看是否返回 302 且Location是预期的真实地址二看普通浏览器分支是否返回 200 提示页或 403而不是 302。如果普通浏览器也 302说明来源判断有漏洞优先回看checkSourceEnv的关键词顺序微信和 QQ 特征词不要互相包含否则会命中错误分支。6.2 把签名自检写进部署脚本支付侧的自检目标是验证签名算法是否和支付平台一致。下面这段命令可以直接跑php -r require sign.php; \$params [pid1000,typealipay,out_trade_noTEST001,notify_urlhttps://j.example.com/n,return_urlhttps://j.example.com/r,name测试,money1.00,sign_typeMD5]; echo sign(\$params, test_key), PHP_EOL; 把这个输出值和支付平台上同样参数计算出的签名比对。不一致时先查ksort排序是否覆盖了所有参与签名的参数再查 URL 编码方式是否在签名前后发生了变化。这里有个高频坑直接拿http_build_query的结果参与签名字符串拼接导致特殊字符被编码后两边算出的签名永远不一致。签名用的字符串必须来自原始参数数组顺序固定、不做任何编码。最后还有一个高频问题接口返回“请在微信或QQ中打开”但用户确实是从微信进来的。这种情况下直接在接口里输出var_dump($_SERVER[HTTP_USER_AGENT])和手机上的真实微信 UA 逐字符对比通常能在同一分钟内发现问题出在关键词缺失还是代理改写。修完关键词集合再跑一遍 6.1 的 curl 回归三个分支状态码全部符合预期后这个直连防红网关才算真正上线。本文还有配套的精品资源点击获取