简介:梦幻防红cos系统后台版是一套围绕网站防红场景设计的工具包,主要面向个人站长、网站管理员及中小型在线平台,用于解决DDoS攻击、恶意刷流量等导致的正常访问被拒绝问题。通过后台自定义防红接口,管理员无需深入底层代码即可调整防护策略,登录域名下的admin.php入口即可进行配置;同时内置install.php一键安装逻辑,当PHP版本不适配时,只需切换至7.0版本即可完成部署,降低了非专业用户的实施难度。资源压缩包共82个文件、总大小624KB,其中包含64个PNG页面素材、8个CSS样式表、7个PHP核心功能文件,以及JS脚本、TXT安装说明和JPG图片,整体结构清楚,界面素材与逻辑文件相互分离,便于快速部署、查看和二次调整。目前已有108人学习使用,适合流量较大、易受攻击的网站快速搭建可自主控制的防红机制,为站点持续稳定运行提供实用保障。压缩包内的安装说明.txt记录了部署要点,便于对照操作;后台功能文件划分明确,适合有二次开发需求的用户参考。
1. 梦幻防红cos系统带后台版无加密:先搞清楚你在部署什么
做推广落地页和短链服务的人,大概率遇到过这种场景:链接刚发出去半小时,在微信里点开就变成「已停止访问该网页」,而同一台服务器用浏览器直开完全正常。所谓防红系统,解决的是「同一个页面在不同聊天软件里可达性不一致」的问题——页面本身合规,但域名被误判、被标记、被加入了拦截名单,导致推广链路断裂。梦幻防红cos系统这类项目,名字里的编号或代号可以不管,真正做的事情就两类:实时检测域名在各平台的访问状态,以及在前端落地页按检测结果自动切换可用域名。
带后台版意味着有独立的管理界面,可以维护域名池、查看检测记录、开关策略开关。无加密则更具体:PHP源码没有做混淆或扩展加密,数据库结构和业务逻辑可以直接读、直接改。这对做二次开发的人是刚需,因为域名检测的阈值、超时时间、切换策略如果封装成黑盒,出了问题只能换项目。能打开源码看检测接口挂了之后是默默跳过还是直接报错,是决定这套系统能不能在真实流量下跑稳的关键。
这篇按部署一套「带后台、无加密」的防红系统顺序来讲:先梳理检测与切换原理,再本地跑通,然后上生产配置,最后落地到监控和二次开发。不依赖任何特定开源项目,通用做法为主。
2. 防红的底层逻辑:域名检测、轮询切换与落地页匹配
2.1 检测机制在检测什么:拦截页面的特征识别
平台拦截一个域名,用户最终看到的不是一个HTTP错误,而是一个平台自己的提示页。防红检测做的,就是用服务端模拟请求去访问目标域名,然后从响应内容里判断「这次访问是否被拦」。
常见的拦截特征有三个层面。第一是状态码:被拦时经常返回302跳到平台的安全提示页,最终页面的HTTP状态可能是200或403,单看状态码不够,要看最终URL的host是不是平台的域。第二是页面标题关键词:比如微信拦截页的标题会包含「已停止访问」「网页包含诱导分享」这类固定文案,检测程序用正则匹配标题命中率极高。第三是响应体长度和敏感标记:正常落地页的HTML体有一定结构,被拦时返回的是一张提示图片加固定文案,DOM结构简单到异常。
实际工程里,多数防红系统的做法是维护一组「拦截特征词库」,每过一段时间检测一次,只要命中特征之一就判定为红(被拦截),命中不了就放行。很重要的坑是:用服务器本机curl去探测,和你手机在微信内打开看到的可能不一样。因为平台拦截判定会结合请求UA、来源referer甚至是账号权重,服务器模拟的UA太干净反而不会触发拦截。所以成熟的检测程序会伪装UA,带上微信内置浏览器的X5内核标识,有时还要带一个真实的referer地址。
2.2 最小可用的域名检测函数
在本地跑通之前,先把最核心的检测函数写出来。这是一个不依赖框架的PHP函数,输入一个域名和检测参数,输出该域名当前的可访问状态。
<?php /** * 检测域名在指定平台是否被拦截 * @param string $url 要检测的完整URL,如 https://a.example.com/?id=1 * @param int $timeout 单次请求超时,单位秒 * @param array $keywords 拦截特征关键词列表 * @return array ['ok' => bool, 'code' => int, 'hit' => string] */ function checkDomainBlocked($url, $timeout = 8, $keywords = ['已停止访问', '网页包含诱导', '被封禁']) { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $timeout, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 5, CURLOPT_USERAGENT => 'Mozilla/5.0 (Linux; Android 10; 微信 XWEB) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 MicroMessenger/8.0.30', CURLOPT_REFERER => 'https://weixin.qq.com/', CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, ]); // 只需要最终URL的host,用来判断是否已被重定向到平台提示页 $finalUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); $body = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // 1. 如果最终落在平台的域名下,直接判定为拦截 $blockedHosts = ['weixin110.qq.com', 'weixin.qq.com', 'c.pc.qq.com']; $finalHost = strtolower(parse_url($finalUrl, PHP_URL_HOST) ?? ''); foreach ($blockedHosts as $bh) { if (strpos($finalHost, $bh) !== false) { return ['ok' => false, 'code' => $code, 'hit' => "redirect_to_{$bh}"]; } } // 2. 如果返回内容命中拦截特征词,判定为拦截 foreach ($keywords as $kw) { if (mb_strpos($body, $kw) !== false) { return ['ok' => false, 'code' => $code, 'hit' => "keyword_{$kw}"]; } } return ['ok' => true, 'code' => $code, 'hit' => 'none']; }逻辑说明:函数先跟随重定向拿最终地址,再看最终host是否落在平台提示页域名下,这是判断是否被拦的最快路径。接着用预设关键词去匹配响应体,覆盖那种「状态码正常但页面内容已经被替换成提示页」的情况。最后返回一个结构化数组,上层就可以拿这个结果做后续的域名切换。
参数说明:$timeout建议8到10秒之间,太短误判率高,移动网络下正常页面打开也要3到6秒;太长检测任务会积压。$keywords里的词要跟着你实际观察到的拦截页文案走,不同版本的客户端提示文案有差异,生产环境建议每季度更新一次词库,直接抓一次真实拦截页把标题和正文里的固定词补充进去。
2.3 落地页如何决定跳到哪个域名
检测脚本解决「知道哪个域名挂了」的问题,落地页解决「用户来了往哪跳」的问题。常见的实现方式是:落地页HTML里维护一份域名列表,优先请求后端接口拿到当前可用域名的排序,然后JavaScript在页面加载时执行跳转。
一种轻量做法是接口只返回状态,不返回页面内容:
// 页面加载后,先从 /api/domain-list 拿到可用域名列表 fetch('/api/domain-list?site_id=1') .then(r => r.json()) .then(data => { if (data.code === 0 && data.data.domains.length) { // 当前页面的域名如果不在可用列表里,跳到第一个可用域名 const current = location.hostname; if (data.data.domains.indexOf(current) === -1) { location.replace('//' + data.data.domains[0] + location.pathname + location.search); } } }) .catch(() => { // 接口挂了不要阻塞用户,停留在当前页面即可 });这里有个容易被忽略的问题:如果接口本身就部署在被封的域名下,那前端请求接口也拿不到数据,跳转逻辑直接失效。所以api接口域名和落地页域名必须解耦,接口固定在独立域名或者直接部署在多个备用域名中都有的那台服务器上。
跳转历史记忆也值得做。用户第一次通过A域名进来,被切到B域名访问了内容,下一次他在聊天记录里点开的仍然是A域名那条链接。体验好的做法是通过localStorage记录最近一次成功跳转的目标域名,二次访问时不再请求接口,直接跳记忆中的域名,减少一步等待。
2.4 域名池的组成与权重轮询策略
| 参数 | 建议值 | 说明 |
|---|---|---|
| 主域名数量 | 1个 | 作为默认下发地址,流量优先走它 |
| 备用域名数量 | 3-5个 | 分布在不同的DNS服务商,避免一起被拔线 |
| 检测间隔 | 每5-10分钟 | 太频繁容易被平台反制,太稀疏切换不实时 |
| 失败阈值 | 连续2次判定拦截 | 避免单次误判就把域名摘除 |
| 恢复检测 | 每30分钟一次 | 被摘除的域名要周期性重测,恢复后自动加回 |
轮询策略上,我见过最简单有效的是「主域名优先,按权重切换」。主域名在线时100%流量走主域名,主域名被摘除后,请求转到备用域名池,按数组顺序依次尝试。每次跳转前在前端再发一次轻量探测请求,用204或200来判断备用域名此刻是否真的能打开,能开才跳。
这个策略的关键是把「服务端检测状态」和「用户实时探测」两层配合起来。服务端检测负责更新整体状态,用户实时探测负责兜底,两者即使有短暂状态不一致,也不至于把用户带到打不开的域名上。
3. 带后台版源码的结构拆分:无加密版本地跑通的最小路径
3.1 典型目录结构:入口、后台与公共配置
这类带后台的系统拿到手,目录结构大同小异。一般来说会有四个区域:前台落地页、后台管理界面、API接口层、安装与配置文件。下面是常见的布局:
project_root/ ├── index.php # 前台入口,负责落地页展示与跳转 ├── admin/ │ ├── index.php # 后台登录入口 │ ├── dashboard.php # 后台首页,展示域名状态总览 │ └── domain.php # 域名池管理页 ├── api/ │ ├── domain-list.php # 向前台下发可用域名列表 │ └── check.php # 手动触发某域名检测 ├── config/ │ ├── database.php # 数据库连接配置 │ └── settings.php # 全局参数,如检测间隔、关键词库 ├── install/ │ └── install.sql # 初始化表结构 └── includes/ ├── function.php # 公共函数库 └── Detect.php # 检测逻辑封装类无加密带来的第一好处是这里可以直接进config/settings.php改参数,不需要在后台找半天设置项。我看到很多项目的参数实际写死在代码里,后台界面上只有一个「保存」按钮,保存了也没生效,原因就是后台写的是另一个变量名。源码可读的话,先搜变量名再改,能省很多排错时间。
3.2 数据库初始化与最小表结构
install.sql里一般会建多张表,核心三张就够了:域名表、检测记录表、用户表。域名表用来存储主域名和备用域名,检测记录表存每次检测的结果用于展示趋势,用户表是后台账号。
CREATE TABLE `domain_pool` ( `id` int(11) NOT NULL AUTO_INCREMENT, `domain` varchar(190) NOT NULL COMMENT '完整域名,不带http', `type` tinyint(1) NOT NULL DEFAULT '2' COMMENT '1主域名 2备用域名', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1正常 0被摘除', `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序权重,越小越优先', `remark` varchar(255) DEFAULT '' COMMENT '备注,如DNS服务商', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_domain` (`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `check_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `domain_id` int(11) NOT NULL DEFAULT '0', `domain` varchar(190) NOT NULL DEFAULT '', `result` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1通过 0拦截', `hit_reason` varchar(100) NOT NULL DEFAULT '' COMMENT '命中特征', `checked_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_domain_id` (`domain_id`), KEY `idx_checked_at` (`checked_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;字段说明:type字段区分主域名和备用域名,是为切换逻辑服务的。sort权重字段要注意,很多系统在后台界面上是拖拽排序,但实际SQL查询时候用的是ORDER BY sort ASC,整数越小越靠前,别下意识以为是越大越靠前。check_log表要定期清理,一天检测144次(10分钟间隔),5个域名就是720条数据,一个月下来2万条,不清理的话后台查询会变慢。
3.3 用PHP内置服务器在本地跑通
拿到源码第一件事不是配置nginx,而是先用PHP自带的内置服务器在本地确认代码能跑。
# 在项目根目录启动内置服务器,端口选80或8080 php -S 0.0.0.0:8080 -t . # 如果在Windows下开发,注意php命令要在PATH中,或者写完整路径 # D:\phpstudy_pro\Extensions\php\php8.0.2nts\php.exe -S 127.0.0.1:8080 -t .启动后浏览器访问http://127.0.0.1:8080/,如果页面是空白或直接弹出下载框,优先检查config/database.php里的数据库账号密码。这一步跑通的意义是排除环境问题,先把代码层面的事故面缩小到「能用内置服务器启动」的程度。
注意内置服务器不支持public目录下的入口文件重写,所以访问后台时要带路径。比如后台入口如果是admin/index.php,那就直接访问http://127.0.0.1:8080/admin/index.php,不要期待能省略index.php。这不代码有问题,是PHP内置服务器的路由能力比nginx弱,本地调试时访问完整路径即可。
3.4 后台管理系统的登录态与入口逻辑
后台的登录逻辑无外乎「表单提交账号密码,比对数据库,写入session」。不同项目差别在于校验方式,有些是明文比对,有些是md5后比对。无加密版本可以直接看login.php里的代码确认。重点检查两个点:一是退出登录时是否销毁session,二是登录成功后跳转是否带权限判断。
这里给一个后台鉴权的通用模板,做二次开发时照着写就行:
<?php session_start(); // 未登录跳转到登录页 if (empty($_SESSION['admin_id'])) { header('Location: /admin/login.php'); exit; } // 可选的接口鉴权:校验来源和token,防止CSRF if (!empty($_POST) && (!isset($_POST['token']) || $_POST['token'] !== $_SESSION['token'])) { http_response_code(403); exit('非法请求'); }参数说明:$_SESSION['admin_id']是登录标记,很多系统只判断这一个变量,存在一定的会话固定风险,好在后台一般跑在内网或加了IP白名单,攻击面不大。token校验是给后台里的「手动检测」「删除域名」这类POST操作加一层防跨站请求伪造的保险,做二次开发时建议保留。
4. 部署到生产:伪静态、数据库配置与检测任务调度
4.1 nginx伪静态规则与TinkPHP路由兼容
很多防红系统基于TinkPHP或Laravel开发,前台入口只有一个index.php,其余路径全部交给路由解析。nginx下伪静态写不对,最典型的症状是「首页能开,内页404」。
以TinkPHP的常见配置为例:
server { listen 80; server_name a.example.com; root /var/www/example/public; index index.php index.html; location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=$1 last; break; } } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { expires 7d; access_log off; } }配置说明:rewrite ^(.*)$ /index.php?s=$1是兼容TinkPHP pathinfo模式的写法,把/index/check?url=xx这类路径统一到了index.php的s参数上。很多部署失败的原因在于root指向了项目根目录而不是public子目录,导致nginx找不到入口文件。静态文件的expires 7d不是必须的,但对落地页首屏有实际帮助,降一次重复访问的静态资源请求量就少一次。
4.2 域名池批量录入与状态同步
上线前要把主域名和备用域名批量录入。直接在phpMyAdmin或命令行里操作SQL比较快,后台界面适合运维时的单条增删。
# 批量插入备用域名,这样写可以避免重复插入报错 INSERT INTO domain_pool (`domain`, `type`, `status`, `sort`) VALUES ('a.example.com', 1, 1, 0), ('b.example.com', 2, 1, 10), ('c.example.com', 2, 1, 20), ('d.example.com', 2, 1, 30) ON DUPLICATE KEY UPDATE `status` = VALUES(`status`);这里要特别提醒:备用域名不要用同一家DNS服务商和同一个CDN账号。域名被封时,如果所有备用域名都在同一账号下的同一个CDN里,平台封禁可能是按CDN节点或按账号维度来的,会出现「全灭」的情况。我见过最稳的做法是多个域名分别放在阿里云、腾讯云、Cloudflare三个不同服务商,就算某一个平台的封禁规则再激进,至少不会一次全废。
另外域名要提前备案还是不需要,取决于实际业务,代码层面控制不了。但域名池里混用已备案和未备案的域名时,检测结果会差异很大,未备案域名在某些网络环境下本身就不可达,会被误判为「拦截」。建议域名池只放同一资质等级的域名,这样检测数据才有一致性。
4.3 检测频率与失败阈值的关系
检测频率和失败阈值是需要联合调的两个数。频率决定「发现域名挂了的速度」,阈值决定「误判容忍度」。下面这组参数是经过实践的经验值:
| 配置项 | 取值 | 效果 |
|---|---|---|
| 检测间隔 | 300秒 | 5分钟一轮,发现被摘除的平均时延在5-10分钟 |
| 单次超时 | 8秒 | curl请求8秒无响应算失败 |
| 连续失败阈值 | 2次 | 连续两次检测都判定拦截才执行域名摘除 |
| 恢复检测间隔 | 1800秒 | 被摘除域名每半小时测一次,恢复后自动加回池子 |
| 摘除后保留时间 | 24小时 | 被摘除域名大于24小时不恢复,自动通知管理员 |
连续失败阈值设成2,是为了抵抗单次误判。比如微信那边偶尔抽风,对正常域名也返回一次拦截页,过五分钟再测又正常了,如果阈值为1,这个域名就被白白摘除了。反过来,阈值为3或以上时,用户在这15分钟内访问主域名会发现打不开,因为系统还在等待第三次检测结果出来。阈值2是「发现速度和误判率」的平衡点。
4.4 部署期典型故障:白屏、验证码不显示、接口超时
部署期遇到的高频问题,按出现概率排序如下:
后台登录页打开是白屏,大概率是PHP报错被隐藏了。查看php.ini里display_errors是否为On,或者在入口文件加一行ini_set('display_errors', 1);让错误显示出来。既然是无加密版本,这一步是允许的,生产环境再关回去。
验证码不显示,通常是GD库没装。Linux下执行apt install php-gd或yum install php-gd,重启php-fpm就好。判断方法很简单:PHP探针里看gd扩展是否存在,不存在那验证码必然出不来,不是代码问题。
api接口请求超时,先确认是不是被摘除域名的问题。有些项目接口地址写的是主域名,主域名被封后接口自然不可达,这时要在配置里把接口地址改到独立可用的域名,或者改用IP访问测试。还有一个隐蔽问题是curl的CURLOPT_DNS_CACHE_TIMEOUT没设置,DNS解析结果被缓存太久,域名已经换了IP但curl还在用旧IP。显式设置curl_setopt($ch, CURLOPT_DNS_CACHE_TIMEOUT, 60)可以缓解。
5. 把「无加密」用起来:自己加一套域名健康度监控与通知
5.1 基于cron的定时检测与通知脚本
系统自带的检测任务可能只更新数据库状态,但不会告诉你「主域名已经掉了,快去处理」。自己写一个独立的监控脚本,挂在cron上,能在第一时间发通知。
<?php // monitor.php 独立监控脚本,放在项目根目录 require __DIR__ . '/includes/function.php'; $pdo = new PDO('mysql:host=localhost;dbname=your_db', 'user', 'pass'); $stmt = $pdo->query("SELECT id, domain FROM domain_pool WHERE status = 1 AND type = 1"); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $url = 'https://' . $row['domain'] . '/'; $result = checkDomainBlocked($url, 8); if (!$result['ok']) { // 连续第一次失败,先不发通知,等第二次确认 $failKey = 'fail_' . $row['id']; $failCount = (int)apcu_fetch($failKey) ?: 0; $failCount++; apcu_store($failKey, $failCount, 600); if ($failCount === 2) { sendDingTalk("域名被拦截:" . $row['domain'] . ",命中特征:" . $result['hit']); apcu_delete($failKey); } } else { apcu_delete('fail_' . $row['id']); } }监控逻辑说明:脚本单独跑在cron里,不依赖防红系统自带的调度器,这样可以避免「系统整体挂了连监控都发不出来」的死锁。连续失败计数用APCu缓存保存,存10分钟过期,这样跨两轮检测(5分钟间隔)能凑齐2次失败才触发通知。
cron配置示例:
*/5 * * * * /usr/bin/php /var/www/example/monitor.php >> /var/log/domain_monitor.log 2>&1提示:通知渠道可以用钉钉机器人Webhook,也可以用Server酱之类的服务。如果都没有,写一个发邮件的也行,核心是「被摘除」和「恢复」两个事件必须通知,日常正常检测结果不需要通知,避免告警疲劳。
5.2 给后台增加一个健康度趋势页
系统自带后台多半只有「当前状态」列表,没有历史趋势。既然是无加密版本,在admin目录下加一个文件成本很低,而且能做出一个类似vue3后台管理体系里常见的dashboard效果。
新增接口返回近24小时检测数据,按小时聚合:
<?php // api/health-trend.php header('Content-Type: application/json; charset=utf-8'); $pdo = new PDO('mysql:host=localhost;dbname=your_db', 'user', 'pass'); $sql = "SELECT DATE_FORMAT(checked_at, '%H:00') AS hour, COUNT(*) AS total, SUM(result = 0) AS blocked FROM check_log WHERE checked_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR) GROUP BY DATE_FORMAT(checked_at, '%H:00') ORDER BY hour ASC"; $rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC); echo json_encode(['code' => 0, 'data' => $rows]);聚合说明:result = 0的计数是SQL里的布尔表达式求和,MySQL会把true当1、false当0,SUM(result = 0)直接统计出拦截次数。前端拿这个JSON就能画出每小时拦截次数折线图,用于观察平台封禁是否存在固定时间段规律。
5.3 一个提升运维效率的小技巧:主动预热
即使系统没有自动恢复功能,被摘除的域名在平台解封后,用户也不会马上知道。手动去后台改状态太慢,可以加一个「主动预热」机制:监控脚本里对被摘除超过1小时的域名,每小时主动访问一次,如果连续两次检测正常,自动把数据库里的status改回1,让域名自动回归域名池。
这个技巧的本质是把「人工确认解封」变成「自动探测+自动恢复」。加了这个逻辑后,备用域名池的存活率会明显上升,因为很多域名的封禁其实是短时的,24小时恢复检测间隔太长,错过了解封后的一大波自然流量。
到这里,整个防红系统的可运维性才算完整:检测有准度、切换有兜底、失败有通知、恢复无人工。剩下的就是根据实际业务的域名数量去调参数,以及盯着前两周的检测日志看误判率了。
本文还有配套的精品资源,点击获取