做个人链接分享、推广落地页或者给自己的产品挂二维码的朋友,应该都遇到过一件特别头疼的事:链接明明没毛病,发出去却被人反馈“已停止访问”,或者直接在聊天窗口里变成一段无法点击的灰色文字。圈子里管这套麻烦叫“红”,而专门解决这个问题的系统,就是“防红系统”。我前前后后用PHP和MySQL写了两版防红源码,从最开始的单纯短链跳转,到后来加入落地页、域名健康检测、UA识别这些模块,踩了不少坑。今天我就把整套源码的架构思路、关键代码、部署流程和排查经验完整拆开讲一遍。
这篇东西适合谁看呢?一是准备自己做短链接服务、想搞清跳转原理的开发者;二是运营个人网站或产品页、经常需要在外部分享链接的朋友;三是刚接触PHP源码、想找一个既能练手又能直接上线的实战项目的初学者。我会尽量把每段代码为什么要这么写、每个配置解决什么问题都讲透,你照着操作也能复现一套。
1. 防红系统的核心逻辑与整体设计
1.1 防红到底防的是什么
很多人第一次听到“防红”,会以为和颜色有关,其实它指的是防止链接被平台风控系统标记。你在聊天软件、浏览器或者各种内容平台里发一条链接,平台会先做一遍安全检测:看域名有没有被举报过、看目标页面内容是否合规、看跳转行为是不是异常。一旦命中风控规则,轻则链接打不开,重则整个域名被拉黑,后面所有用这个域名的链接全部遭殃。
所以防红系统的本质,不是去挑战平台规则,而是做两件事:一是尽量让链接看起来像正常访问,减少被误判的概率;二是当某一个入口域名已经被拦截时,能够快速切换到备用域名,保证用户始终有一个能打开的入口。说白了,它是一套围绕“域名可用性”和“跳转体验”的管理工具,而不是什么黑魔法。
我在设计第一版源码时,误以为只要做个302跳转就行,结果上线没几天就发现微信里根本打不开,原因就是没有做UA识别,也没有落地页缓冲,跳转动作太生硬。后来重写时,我把“识别访问环境”和“动态选择跳转方式”作为核心需求,代码量翻了一倍,但可用性提升非常明显。
1.2 短链接与中转跳转的整体思路
防红源码的基本骨架是短链接服务。用户访问你给的短链接地址(比如 https://yourdomain.com/Ab3x9),请求先到达你自己的服务器,由PHP脚本解析出短码,去数据库查出对应的真实目标地址,然后根据访问者的User-Agent、Referer、设备类型等信息,决定是直接302跳转,还是先展示一个落地页让用户手动点击。
这个中间层非常关键。因为直接302跳转在某些环境下容易被拦截,而通过落地页让用户主动点击,相当于把“自动跳转”变成了“用户行为”,误判概率会降低。同时,中转层还承担了数据统计的功能,你可以记录每次点击来自哪个页面、什么设备,后续做推广效果分析也有数据支撑。
源码的整体目录我习惯这样组织:
text复制.
├── admin/ # 后台管理模块
│ ├── login.php # 后台登录
│ ├── link_list.php # 链接列表
│ ├── domain_list.php # 域名管理
│ └── setting.php # 系统配置
├── api/ # 对外接口
│ ├── create_link.php # 生成短链接口
│ └── check_domain.php # 域名健康检测接口
├── config/
│ └── config.php # 数据库及基础配置
├── install/
│ └── install.sql # 初始化数据库脚本
├── index.php # 前端入口,处理短码跳转
└── landing.php # 落地页模板
1.3 为什么我选了PHP而不是Python或Node
开发这套源码时,我对比过Python Flask、Node.js Express和PHP的原生写法。最终选PHP,理由很实际。
首先是部署成本。PHP几乎是虚拟主机和宝塔面板的默认支持项,上传源码改个配置就能跑,不依赖常驻进程。Python和Node虽然开发体验好,但部署时需要单独管理进程、配置反向代理,对不熟悉服务器操作的朋友来说门槛高了一截。
其次是生态和改造成本。PHP的MySQL操作、Session管理、文件上传都极其成熟,网上能找到大量现成的后台模板可以直接融合。Python后端我也写过,如果你本身更熟悉Python,用Flask写一个防红系统完全可行,检测UA、读Referer、返回302这些逻辑在其他语言里也都是一样实现,只是部署和进程守护要额外花点心思。
最后是系统资源占用。一个日访问量几千次的个人短链服务,PHP-FPM加MySQL的常驻内存开销比Node常驻进程低不少,在1核1G的小机器上跑得很稳。如果以后量大了,再重写成Go或者Java也不迟,核心的跳转逻辑和库表结构都是通用的。
1.4 源码里最核心的防红策略有哪些
我梳理了一下,一套成熟的防红源码至少包含这么几个策略:
- 多域名冗余:系统里维护多个可用域名,当默认域名被拦截时自动切换到备用域名,用户拿到的始终是短链,但背后的域名可以悄悄更换。
- UA识别与分端跳转:识别微信内置浏览器、手机浏览器、PC浏览器,不同的环境下走不同的跳转逻辑。
- 落地页中转:不直接跳转,而是展示一个“点击继续访问”的页面,把自动跳转变成用户主动操作。
- 域名健康检测:定时任务去检测每个域名的可访问状态和ICP备案状态,提前发现风险。
- 内容降级与备用入口:当目标地址存在风险时,提供二维码或者备用链接,防止用户彻底丢失。
这些策略不是拍脑袋想出来的,全部来自实际使用中遇到的问题。你会在后续的代码里看到它们的具体实现。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心模块源码拆解与原理
2.1 链接生成模块:短码算法与数据库设计
防红系统的起点是生成短链。短链的短码我建议用自增ID加base62编码实现,而不是直接生成随机字符串。原因是自增ID天然不会重复,短码长度短,而且不易碰撞。
php复制function generateShortCode($id) {
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$code = '';
while ($id > 0) {
$code = $chars[$id % 62] . $code;
$id = intval($id / 62);
}
return $code === '' ? '0' : $code;
}
这个函数的作用是把数字ID转换成62进制的短码。比如ID为100000,转换后是 q0U 这样的短码,放在URL里非常精简。访问时再根据短码反向查出ID,就能定位到对应的目标地址。数据库里我用了一张 links 表来存链接数据:
sql复制CREATE TABLE `links` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`short_code` varchar(16) NOT NULL,
`target_url` varchar(500) NOT NULL,
`landing_url` varchar(500) DEFAULT NULL,
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1启用 0禁用',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `short_code` (`short_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这里有两个容易被忽略的细节。一是 target_url 必须允许长文本,因为有些推广链接带了一长串追踪参数,几百个字符很正常。二是务必给 short_code 加唯一索引,否则极端情况下会出现短码重复,到时候排查起来很痛苦。
后台新增链接时,PHP脚本接收目标地址、可选落地页、备注等信息,插入数据库后用 mysql_insert_id() 拿到自增ID,再调用 generateShortCode() 生成短码,更新到这条记录里。整个逻辑很直白,但它是整套系统的地基,地基不稳后面全白搭。
2.2 访问检测模块:UA、Referer与设备识别
访问者点击短链后,index.php 会做一系列判断。我直接贴出关键代码:
php复制$code = $_GET['code'] ?? '';
if ($code === '') {
http_response_code(404);
exit('链接不存在');
}
// 查询数据库获取链接信息
$link = getLinkByCode($code);
if (!$link || $link['status'] != 1) {
http_response_code(404);
exit('链接已失效');
}
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$referer = $_SERVER['HTTP_REFERER'] ?? '';
$isWechat = strpos($ua, 'MicroMessenger') !== false;
$isMobile = preg_match('/Mobile|Android|iPhone|iPad/i', $ua);
为什么要单独判断微信内置浏览器呢?因为微信对自动跳转的拦截最严格,直接302跳转经常被提示“非微信官方网页”。所以当 $isWechat 为真时,我会优先展示落地页;而普通手机浏览器和PC浏览器,则可以直接302跳到目标地址,体验更顺畅。
Referer的判断也有讲究。如果访问者是从聊天窗口或者某些平台点进来的,Referer会暴露来源。有些平台会对带特定Referer的跳转做额外检测,所以我的源码里允许后台配置“过滤Referer”的规则,命中规则时强制走落地页,避免暴露完整的目标地址。
注意一个细节:GET参数里的 code 必须做过滤和长度校验,不要直接把用户输入拼进SQL,否则容易被SQL注入。我建议用PDO预处理或者至少用 mysqli_real_escape_string() 转义一遍。网上很多开源源码死在这一步,看着功能齐全,用起来两天就被打穿。
2.3 落地页与跳转逻辑的参数协作
落地页在这套系统里承担着“缓冲”作用。完整的跳转逻辑是这样的:
php复制if ($link['landing_url'] != '' && shouldUseLandingPage($ua, $referer)) {
// 展示落地页
header('Location: landing.php?code=' . $code);
exit;
}
// 直接302跳转
header('Location: ' . $link['target_url'], true, 302);
exit;
shouldUseLandingPage() 是我封装的一个判断函数,逻辑大概是:优先判断是否是微信内置浏览器,是则使用落地页;再看目标地址是否命中风险关键词,命中则使用落地页;最后看后台全局设置里是否开启了“强制落地页”开关。
落地页本身很简单,就是一个带说明文字的HTML页面:
html复制<h3>即将访问目标页面</h3>
<p>请点击下方按钮继续,页面将在新窗口打开。</p>
<a href="真实地址" id="targetBtn">继续访问</a>
这里有个实操小技巧:落地页上的“继续访问”按钮最好加 rel="noopener noreferrer",并且把目标地址用JavaScript动态写入,而不是直接写在HTML源码里。这样既能在一定程度上防止目标地址被爬虫抓取,也能减少原页面被恶意反向引用的风险。
2.4 域名管理与自动健康检测机制
域名是防红系统最脆弱的资源,所以源码里一定要有专门的域名管理模块。我在 domains 表里维护了每个域名的状态、权重、检测时间:
sql复制CREATE TABLE `domains` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`domain` varchar(128) NOT NULL,
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1可用 0停用',
`is_primary` tinyint(1) NOT NULL DEFAULT '0',
`last_check_time` datetime DEFAULT NULL,
`check_result` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
生成短链时,程序会从 domains 表里挑一个 status=1 且权重最高的域名作为前缀。当用户访问 https://domain1.com/Ab3x9 发现打不开时,系统没法自动改用户已经访问的地址,所以必须在链接生成时就尽量分配当前最健康的域名。
为了做到“提前发现风险”,我写了一个 checkDomainHealth() 函数,配合系统的Cron定时任务,每隔10分钟用cURL探测一遍所有域名的HTTP状态码和响应时间:
php复制function checkDomainHealth($domain) {
$ch = curl_init('https://' . $domain . '/ping.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode === 200;
}
这个健康检测的意义在于,你不需要等用户投诉“链接打不开了”才去手动换域名,而是让系统自动把不健康的域名标记为停用,新的短链就不会再分配到这个域名上。已有的短链如果还想救,可以通过后台批量为链接更换域名前缀,虽然旧链接已经失效,但至少给了一个补偿方案。
3. 完整部署实操记录
3.1 环境准备与版本选择
我今年重新部署这套源码时,用的是阿里云轻量服务器的2核4G配置,操作系统选的CentOS 7.9,面板用的宝塔Linux面板。PHP选择7.4版本,原因是对老代码兼容性好,同时性能比PHP 5.6提升明显;MySQL选的5.7,稳定且兼容性广;Nginx用1.20版本。
如果你只是想本地测试,用XAMPP或者PHPStudy都行,Windows环境下跑起来也完全没问题,只是注意保管好数据库密码,别把本地配置文件传到公开仓库里去。
PHP的扩展里,有几个必须开:pdo_mysql、curl、openssl、fileinfo。在宝塔面板里安装PHP时勾选这些扩展即可。如果漏了curl,后面域名健康检测和落地页抓取都会失败,这是新手最容易踩的坑。
3.2 上传源码与目录权限设置
将源码整体上传到网站根目录,比如 /www/wwwroot/fanghong。上传完成后,需要给两个目录写权限:runtime/ 目录用于存放缓存和日志,install/ 目录在安装完成后建议删掉或者改成只读,防止被恶意调用。
如果你用宝塔面板创建站点,在“站点设置”里把“运行目录”指向网站根目录,并确保“伪静态”设置为thinkphp或其他兼容规则。实际上我用的是原生PHP,没有框架,所以伪静态规则很简单。在Nginx配置文件中加入:
nginx复制location / {
if (!-e $request_filename) {
rewrite ^/([a-zA-Z0-9]+)$ /index.php?code=$1 last;
}
}
这段配置的作用是,把 https://yourdomain.com/Ab3x9 这样不存在的路径重写到 index.php?code=Ab3x9。如果没有这段规则,访问短链会直接404。
3.3 数据库配置与安装步骤
打开 config/config.php,修改数据库连接信息:
php复制return [
'host' => '127.0.0.1',
'port' => 3306,
'name' => 'fanghong_db',
'user' => 'fanghong_user',
'pass' => '你的强密码',
'charset' => 'utf8mb4',
];
然后在MySQL里创建数据库和用户,并导入 install/install.sql。我给你的建议是不要用root直接连应用数据库,专门建一个只有 select, insert, update, delete 权限的用户,这样即使源码被入侵,损失也可控。
导入完成后,访问 https://yourdomain.com/install/ 如果程序有安装检测页面就按提示操作,如果没有就手动确认数据表已经创建成功。我习惯在 config.php 里加一个 INSTALLED 常量,安装完成后改成 true,防止重装覆盖数据。
3.4 后台配置与第一个短链测试
后台入口是 admin/login.php,默认账号密码在安装完成后会通过日志或数据库 admin_user 表生成。登录后台之后,第一件事是到“域名管理”里添加自己的域名。
添加域名时需要注意,域名必须已经解析到当前服务器,并且SSL证书配置好,否则生成出来的链接在手机上会被浏览器提示不安全。然后到“系统设置”里填写全局参数,比如强制落地页开关、Referer过滤规则、二维码备用地址等。
配置完成后,在“链接列表”里点击新增链接,填一个目标地址,比如 https://example.com/product?id=123,提交后系统会生成一个短码。把短链复制到浏览器里测试,正常情况会跳到目标地址;再拿手机流量打开一次,体验一下移动端跳转是否顺畅。
3.5 上线前必须做的配置与检查
第一次部署成功后,有四个地方一定要检查,否则上线即翻车。
第一,检查Nginx是否开启了HTTPS强制跳转。现在主流平台对HTTP链接的信任度很低,没有SSL的短链基本等于自断一臂。在宝塔面板里给域名申请Let's Encrypt证书,并在站点配置中设置“强制HTTPS”。
第二,检查PHP的 max_execution_time 和 memory_limit。短链接跳转本应该很快,但数据库查询慢或者网络波动时,默认30秒超时反而会拖垮体验。我把它调成 max_execution_time=60,memory_limit=128M。
第三,配置好日志切割。防红系统是典型的高访问低存储场景,Nginx和PHP日志如果不做按天切割,半年就能塞满磁盘。宝塔自带日志切割功能,按天保留30份即可。
第四,测试Cron任务是否正常运行。域名健康检测依赖Cron,我在crontab里写的是:
bash复制*/10 * * * * php /www/wwwroot/fanghong/api/check_domain.php >> /www/wwwroot/fanghong/runtime/check_domain.log 2>&1
任务执行后,可以去 domains 表看 last_check_time 有没有更新,如果一直没更新,说明Cron没跑起来,要么是路径不对,要么是执行权限有问题。
4. 常见问题与排查技巧实录
4.1 短链打开404,伪静态规则没生效
这是出现频率最高的问题。如果你访问短链时看到404,先确认Nginx配置文件里是否加入了重写规则,并且在“伪静态”里选择了对应的规则。手动加了规则后,记得要重载Nginx配置:
bash复制nginx -t
nginx -s reload
如果Nginx配置文件没问题,再检查站点根目录是否有 index.php,以及运行目录是否指向了正确的位置。很多面板创建站点时会默认把运行目录指向 public,导致入口文件找不到。
4.2 点击短链直接弹出源码或文件下载
这个问题的原因基本是PHP没有正确解析。你访问 https://yourdomain.com/index.php 时,如果浏览器直接显示PHP源码,说明Nginx没有把 .php 文件交给PHP-FPM处理。
在宝塔面板里检查“PHP版本”是否已正确绑定到站点,正常情况下站点配置里应该有类似这样的代码:
nginx复制location ~ \.php$ {
fastcgi_pass unix:/tmp/php-cgi-74.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
如果这段配置缺失,重新设置站点PHP版本即可。另外,index.php 文件开头一定不要有多余的空格或BOM头,否则会报“headers already sent”的错误。
4.3 同一个链接在PC正常,在微信里打不开
这种分端差异问题,十有八九是UA判断或者落地页逻辑不对。先开启浏览器模拟,把访问端的UA改成微信内置浏览器的UA:
text复制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
然后用这个UA访问短链,看服务端返回的是302还是落地页HTML。如果返回的是302,说明源码没有正确识别微信UA,检查 strpos($ua, 'MicroMessenger') 这行代码是否被缓存覆盖。
还有一种常见情况是,落地页本身被微信拦截了。比如落地页里包含大量外链脚本或自动下载逻辑,微信对这类页面也会标记。落地页尽量保持简洁,一个标题、一段说明、一个按钮就够了。
4.4 后台登录不了,提示验证码错误或Session失效
后台登录问题多出在Session上。PHP原生Session默认把文件存到 /tmp,如果服务器做了磁盘清理或者权限调整,Session文件写入失败,登录状态就存不下来。
解决办法是在 php.ini 里修改Session保存路径,改为一个持久化目录:
ini复制session.save_path = "/www/server/session"
然后重启PHP-FPM。如果用了Redis做Session存储,请检查Redis连接是否正常,密码是否填对。另外,很多防红源码后台没有做登录次数限制,容易被暴力破解,建议你加一个简单的登录失败次数判断,超过5次锁定IP半小时,代码量不大但安全收益很高。
4.5 域名被拦截后如何快速切换
域名被拦截不是偶发事件,我在这套系统跑了两年多,前后换过不少域名。最省事的操作流程是:提前准备至少2到3个备用域名,全部解析到同一台服务器,配置好SSL,在后台“域名管理”中统一添加。
当某个域名被拦截时,后台健康检测会在10分钟内把它标记为停用。新生成的短链会自动使用备用域名,已经生成的短链则需要在后台“链接列表”里批量替换域名前缀。我的源码里做了一个“一键替换”功能,本质上就是执行SQL更新:
sql复制UPDATE links SET target_url = REPLACE(target_url, 'https://old.com', 'https://new.com');
这个方法只对需要更换域名的短链有效,如果短链本身是存在 links 表里的 target_url 字段里的完整地址,那可以整表更新;但如果是分配域名前缀的方式,则需要在跳转时动态读取可用域名,把存储的短码路径拼上去,这样换域名时就不需要动数据库了。我更推荐后者,因为它把域名和链接解耦了,换域名只改配置,不碰数据。
4.6 常见问题速查表
| 问题现象 | 可能原因 | 解决思路 |
|---|---|---|
| 短链访问404 | 伪静态未配置 | 检查Nginx rewrite规则并重载 |
| 浏览器弹出PHP源码 | PHP未解析 | 检查站点PHP版本及fastcgi配置 |
| 微信里打不开 | UA判断失效/落地页被拦截 | 用微信UA模拟测试,简化落地页 |
| 后台无法登录 | Session目录损坏 | 修改session.save_path并重启 |
| 域名很快就失联 | 备用域名不足/健康检测没跑 | 增加备用域名,配置Cron任务 |
| 数据被SQL注入 | 未使用预处理语句 | 用PDO绑定参数或转义用户输入 |
| 短码重复 | 数据库没加唯一索引 | 为short_code字段加UNIQUE约束 |
这张表是我把群里朋友遇到最多的问题整理出来的,基本覆盖了从部署到日常运维的绝大部分故障场景。
最后再说一个我自己的体会。防红系统的技术门槛其实并不高,真正难的是对“合规”和“体验”之间平衡的理解。它只是一个链接管理工具,核心价值在于让你自己管理的域名、链接和页面更稳定地触达用户。我见过有人把它当成钻空子的工具,结果域名一封再封,反而把自己搞得很累。我自己跑这套系统这几年的最大心得是:把内容做干净、把域名养稳、把用户体验做好,比任何花哨的绕过技巧都重要。如果链接本身没问题,这套系统帮你减少误杀;如果内容本身有问题,再多的备用域名也救不回来。
另外补一个小技巧:新域名买回来之后,不要急着拿去生成大量短链,先正常解析、正常访问几天,让域名逐步建立可信记录,再投入正式使用,这样被误判的概率会低很多。这是我换过七八个域名后总结出来的实际经验。
