简介:这是一套开箱即用的短网址生成与防红一体化网站源码,面向Web开发初学者及PHP全栈实践者,解决社交媒体推广、营销链接分发中因平台封禁导致访问中断的实际问题。资源包含73个文件,主体为21个PHP后端逻辑文件(含zise.php、api.php、fanghong.sql等核心模块)、20个CSS样式文件与12个JS交互脚本,辅以字体、图标及图片资源,整体压缩包仅1.1MB,轻量易部署。已有1250人学习下载,说明其在实战教学与二次开发场景中具备良好验证基础。读者可直接搭建完整站点,获得带用户管理、网址增删改查、访问统计、防红策略配置(加密/代理/混淆)的后台系统,同时深入理解短码映射算法、SQL注入/XSS防护机制及前后端协同逻辑,是掌握Web安全与URL服务架构的典型入门级项目。
1. 短网址生成网站源码:不是简单跳转,而是防红链路的完整闭环
你有没有遇到过这样的场景:刚发出去的推广链接,5分钟内就被微信/QQ/钉钉标红、拦截、提示“该网页包含风险内容”;换域名重试,第二天又红;甚至用大厂短链服务(如t.cn、dwz.cn)生成的链接,在某些群聊或私信里照样被折叠、无法点击。这不是玄学,是平台风控策略升级后对「跳转链路可信度」的深度校验——它不只看最终落地页,更盯住中间跳转环节的域名信誉、HTTPS强度、响应头规范、重定向链长度、JS行为特征。所谓“防红源码”,本质是一套可控、可审计、可快速迭代的短链基础设施:从URL编码、数据库存储、HTTP 302跳转控制,到Referer过滤、UA白名单、IP限频、HTTPS强制跳转、HSTS头注入、CSP策略配置,再到日志埋点与红链预警联动。它适合需要高频分发外链的运营团队、私域裂变工具开发者、SaaS产品嵌入式分享模块搭建者,以及对第三方短链服务稳定性存疑的技术负责人。这不是一个“拿来即用”的静态页面,而是一个需部署在自有服务器、可定制风控逻辑、能对接内部用户体系的轻量级Web服务。
2. 用 PHP + MySQL 搭建最小可用短链服务:从零跑通核心跳转链路
短网址生成的核心逻辑极简:用户提交长 URL → 系统生成唯一短码(如abc123)→ 存入数据库 → 用户访问https://yourdomain.com/abc123→ 后端查库 → 302 重定向至原始 URL。但“防红”的关键,恰恰藏在“查库”和“重定向”这两个动作之间的控制权里。我们不用 Laravel 或 ThinkPHP 这类全栈框架,选择原生 PHP + MySQL 组合,原因有三:一是启动快、资源占用低,适合中小流量;二是所有跳转逻辑完全透明,便于插入风控钩子;三是便于后续对接 Nginx 的map模块做前置 UA/Referer 过滤。下面以 PHP 8.1 + MySQL 8.0 为基准环境,构建最小可运行版本。
2.1 数据库设计与初始化:短码必须唯一且带状态字段
防红不是靠“藏”,而是靠“控”。短码不能仅存id, long_url, short_code,必须预留风控干预入口。我们建表时加入status(0=启用,1=禁用,2=灰度)、created_at、updated_at、hit_count(用于识别异常爬虫)、referer_whitelist(JSON 字段,存允许跳转的来源域名列表)等字段。这是后续做 Referer 白名单、灰度放行、自动封禁的基础。
CREATE TABLE `short_urls` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `long_url` text NOT NULL, `short_code` varchar(12) NOT NULL UNIQUE COMMENT '短码,如 abc123', `status` tinyint NOT NULL DEFAULT 0 COMMENT '0=启用,1=禁用,2=灰度', `hit_count` int unsigned NOT NULL DEFAULT 0, `referer_whitelist` json DEFAULT NULL COMMENT '["example.com", "wechat.com"]', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_short_code_status` (`short_code`,`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;提示:
short_code必须加UNIQUE约束,避免重复生成导致跳转错乱;idx_short_code_status复合索引能极大提升查询性能——因为每次跳转都先按short_code查,再过滤status=0。
2.2 短码生成算法:避开易被误判的字符,支持自定义长度
很多开源短链项目用 base62(0-9a-zA-Z),但0Oo、1lI在微信字体下极易混淆,且l(小写L)和I(大写i)常被风控系统标记为“可疑混淆字符”。我们改用精简版 base58:剔除0Oo1lI/+,只保留123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz(共58个字符)。生成逻辑如下:
// utils.php function generateShortCode($length = 6) { $chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; $code = ''; for ($i = 0; $i < $length; $i++) { $code .= $chars[random_int(0, strlen($chars) - 1)]; } return $code; } // 防止生成已存在短码,最多重试5次 function getUniqueShortCode($pdo, $length = 6) { for ($i = 0; $i < 5; $i++) { $code = generateShortCode($length); $stmt = $pdo->prepare("SELECT COUNT(*) FROM short_urls WHERE short_code = ?"); $stmt->execute([$code]); if ($stmt->fetchColumn() == 0) { return $code; } } throw new Exception("Failed to generate unique short code after 5 attempts"); }参数说明:
$length = 6是平衡安全性和长度的常用值。6位 base58 短码理论容量为 58⁶ ≈ 360 亿,远超中小项目需求;若需更高抗爆破性,可设为 7 位(2000 亿+),但注意微信对短链长度无硬限制,过长反而影响传播。random_int()是 PHP 7.0+ 安全随机函数,不可用rand()替代。
2.3 核心跳转控制器:302 重定向前插入 Referer 白名单校验
这是防红最关键的一步。微信/QQ 的风控会检查跳转请求的Referer头是否来自其自家域名(如https://servicewechat.com/、https://im.qq.com/)或空(直接输入地址栏访问)。若Referer是http://evil-site.com,则大概率触发红链。我们在重定向前强制校验:
// redirect.php require_once 'config.php'; require_once 'utils.php'; $shortCode = $_GET['code'] ?? ''; if (empty($shortCode) || !preg_match('/^[1-9A-HJ-NP-Za-km-z]{6,12}$/', $shortCode)) { http_response_code(400); exit('Invalid short code'); } try { $pdo = new PDO($dsn, $db_user, $db_pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]); // 1. 先查状态,禁用/灰度状态直接返回404(不暴露存在性) $stmt = $pdo->prepare("SELECT long_url, status, referer_whitelist FROM short_urls WHERE short_code = ? AND status = 0"); $stmt->execute([$shortCode]); $row = $stmt->fetch(); if (!$row) { http_response_code(404); exit('Not found'); } $longUrl = $row['long_url']; $referer = $_SERVER['HTTP_REFERER'] ?? ''; // 2. Referer 白名单校验(空Referer允许,模拟用户直接访问) if (!empty($referer)) { $whitelist = json_decode($row['referer_whitelist'], true) ?: []; $host = parse_url($referer, PHP_URL_HOST); if ($host && !in_array($host, $whitelist) && !in_array('*', $whitelist)) { // 记录异常访问,但不阻断(避免被探测) error_log("Blocked referer: {$referer} for short code {$shortCode}"); http_response_code(403); exit('Forbidden'); } } // 3. 更新命中次数(异步记录,避免阻塞跳转) $pdo->prepare("UPDATE short_urls SET hit_count = hit_count + 1 WHERE short_code = ?")->execute([$shortCode]); // 4. 强制 HTTPS 跳转(防降级攻击) if (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') { $httpsUrl = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; header("Location: {$httpsUrl}", true, 301); exit; } // 5. 最终302跳转,设置严格安全头 header('Content-Type: text/html; charset=utf-8'); header('X-Frame-Options: DENY'); header('X-Content-Type-Options: nosniff'); header('Referrer-Policy: no-referrer'); header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload'); header("Location: {$longUrl}", true, 302); exit; } catch (PDOException $e) { error_log("DB Error: " . $e->getMessage()); http_response_code(500); exit('Server error'); }逻辑说明:这段代码不是“生成短链”的入口,而是“访问短链”的入口(即
https://yourdomain.com/redirect.php?code=abc123)。它做了五件事:① 校验短码格式合法性;② 查询并确认状态为启用;③ 校验 Referer 是否在白名单(支持*通配);④ 强制跳转 HTTPS;⑤ 发送 302 响应并附带全套安全头。其中Referrer-Policy: no-referrer是关键——它让浏览器在跳转到目标页时不发送 Referer,避免把你的短链域名暴露给下游站点,降低被关联封禁风险。
3. Nginx 层面加固:用 map 指令实现 UA/Referer 前置过滤
PHP 层校验是兜底,但高并发下,恶意请求(如扫描器、爬虫)应在 Web 服务器层就拦截,避免打到 PHP-FPM 进程。Nginx 的map指令能基于请求头动态设置变量,配合if和return实现毫秒级过滤。我们针对两类高危请求做前置拦截:① 明确的爬虫 UA(如python-requests,curl/7.);② 非法 Referer(如已知黑产域名、空 Referer 但非微信/QQ)。
3.1 构建 UA 黑名单 map:精准识别自动化工具
在nginx.conf的http块中添加:
# /etc/nginx/conf.d/short-url.conf map $http_user_agent $bad_ua { default 0; "~*python-requests" 1; "~*curl/[0-9]" 1; "~*wget" 1; "~*Go-http-client" 1; "~*Java/" 1; "~*Apache-HttpClient" 1; "~*okhttp" 1; "~*PostmanRuntime" 1; }参数说明:
map是 Nginx 的映射指令,$http_user_agent是请求头变量,$bad_ua是新变量。~*表示忽略大小写的正则匹配。这里列出的是常见自动化工具 UA 特征,而非真实浏览器(Chrome/Firefox/Safari/WeChat 内置浏览器均未列入)。default 0表示默认不拦截,只有匹配才设为 1。
3.2 构建 Referer 白名单 map:只允许可信来源跳转
同样在http块中添加:
map $http_referer $bad_referer { default 1; # 默认视为非法 "~*https?://[^/]+\.wechat\.com" 0; "~*https?://[^/]+\.qq\.com" 0; "~*https?://[^/]+\.qpic\.cn" 0; "~*https?://[^/]+\.weixin\.qq\.com" 0; "~*https?://[^/]+\.servicewechat\.com" 0; "~*https?://[^/]+\.im\.qq\.com" 0; "~*^$" 0; # 空Referer允许(用户直接输入短链) }注意:
default 1是关键设计——我们采用“黑名单思维下的白名单实现”。即默认所有 Referer 都非法,只显式放行微信、QQ 及其子域名(servicewechat.com是微信小程序域名,qpic.cn是微信图片 CDN 域名,常出现在公众号图文内)。~*^$匹配空字符串,代表用户在浏览器地址栏直接输入短链,这种场景必须放行。
3.3 在 server 块中应用过滤规则:return 444 立即断连
在你的短链站点server块中加入:
server { listen 80; server_name yourdomain.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name yourdomain.com; ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/privkey.pem; # 前置拦截:UA 或 Referer 不合法,立即断连(444 是 Nginx 特有“关闭连接”状态码,不发任何响应) if ($bad_ua = 1) { return 444; } if ($bad_referer = 1) { return 444; } # 正常请求代理到 PHP location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }逻辑说明:
return 444是 Nginx 最高效的拦截方式——它不构造 HTTP 响应包,直接关闭 TCP 连接,对服务器资源消耗趋近于零。相比 PHP 层http_response_code(403),它能在毫秒级阻断恶意扫描,保护后端不被压垮。注意:if在location块外使用是 Nginx 官方允许的(用于请求头判断),无需担心性能问题。
4. 防红避坑指南:5 条血泪经验,每一条都踩过真实翻车现场
做短链防红,最怕的不是技术不会,而是“以为对了,其实错了”。以下是我在线上环境反复验证、被微信/QQ 封过三次后总结的 5 条核心避坑点,现象、原因、解法全部对应真实日志和抓包数据。
4.1 现象:短链在微信内第一次点开正常,第二次点开就红
原因:微信对同一短链的跳转行为有“行为指纹”追踪。若 PHP 跳转时未设置Cache-Control: no-cache, no-store, must-revalidate,微信 WebView 会缓存 302 响应,第二次请求直接读缓存,绕过你的 PHP 校验逻辑,导致 Referer 白名单失效。
解决:在redirect.php的 302 响应头中强制加入缓存控制:
header('Cache-Control: no-cache, no-store, must-revalidate'); header('Pragma: no-cache'); header('Expires: 0');4.2 现象:用 HTTPS 短链跳转 HTTP 目标页,链接立刻被标红
原因:微信/QQ 对混合内容(Mixed Content)极度敏感。当短链域名是 HTTPS,但跳转目标是 HTTP 时,风控系统判定“链路不安全”,直接拦截。这不是浏览器报错,是平台主动封禁。
解决:在生成短链时,前端或后端强制校验long_url协议头。若为http://,拒绝生成,或自动补全为https://(需确保目标站支持 HTTPS)。PHP 中增加:
if (stripos($longUrl, 'http://') === 0) { $longUrl = 'https://' . substr($longUrl, 7); // 后续需验证该 HTTPS 地址是否可访问(用 curl_head 检查 200) }4.3 现象:短链在 QQ 群里能点,但在微信私聊里点不开,提示“网页包含风险”
原因:微信私聊的 Referer 是https://servicewechat.com/...,而 QQ 群是https://im.qq.com/...。你在referer_whitelist里只写了servicewechat.com,漏掉了im.qq.com,导致 QQ 请求被 PHP 层403拦截,但微信误判为“链接本身有问题”。
解决:白名单 JSON 必须同时包含两者:
["servicewechat.com", "im.qq.com", "qpic.cn"]且 Nginx 的map规则也要同步更新,否则前置拦截会先干掉 QQ 请求。
4.4 现象:短链生成后,用 curl 测试 302 正常,但微信里就是打不开
原因:curl 默认不带 Referer,而微信 WebView 会带上。你的 PHP 校验逻辑若对空 Referer 返回 403(而非放行),就会导致 curl 成功、微信失败。
解决:检查redirect.php中 Referer 校验逻辑,确保if (empty($referer)) { /* 放行 */ }分支存在,且位置在白名单校验之前。
4.5 现象:短链域名刚备案,但一周内仍被频繁误判为“仿冒网站”
原因:新域名在微信/QQ 的信誉库中初始分极低。若你的短链页 HTML 中<title>写的是“短链接跳转中…”,或<body>里只有Loading...,会被风控模型识别为“无实质内容的跳转页”,归类为“黑帽SEO工具”。
解决:在跳转页(即redirect.php渲染的页面)中,添加符合平台规范的“过渡页”HTML:
<!DOCTYPE html> <html><head><meta charset="utf-8"><title>正在跳转...</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style>body{font-family:Helvetica,sans-serif;text-align:center;padding:50px;}a{color:#09bb07;}</style> </head><body><h2>正在安全跳转</h2><p>即将前往:<a href="<?= htmlspecialchars($longUrl) ?>"><?= htmlspecialchars(parse_url($longUrl, PHP_URL_HOST)) ?></a></p></body></html>提示:这个 HTML 必须在
header("Location: ...")之前输出,且href使用htmlspecialchars防 XSS。微信要求跳转页有“可读标题+可点击链接+明确目标域名”,否则视为违规。
5. 进阶技巧:用 Redis 缓存热点短码,把 QPS 从 200 提升到 5000+
当短链日活超过 10 万次,MySQL 的单点查询会成为瓶颈。此时不能简单加从库——因为短链查询是典型的“读多写少、Key-Value、高并发、低延迟”场景,Redis 是唯一合理选择。但直接用GET/SET会丢失状态控制(如status字段),我们需要用 Redis Hash 结构,把整条记录缓存起来,并与 MySQL 保持强一致。
5.1 Redis 数据结构设计:Hash 存储 + 过期时间分级
我们不用 String 存long_url,而用 Hash 存整个记录,字段与 MySQL 表对齐:
| Redis Key | Field | Value | 说明 |
|---|---|---|---|
short:abc123 | long_url | https://target.com/xxx | 原始长链 |
short:abc123 | status | 0 | 状态(0=启用) |
short:abc123 | hit_count | 123 | 命中次数(Redis 内原子自增) |
short:abc123 | referer_whitelist | ["servicewechat.com"] | JSON 字符串 |
关键点在于过期时间(TTL)分级设置:
status=0(启用)的短码,TTL 设为86400(24 小时);status=1(禁用)的短码,TTL 设为3600(1 小时),避免长期占用内存;- 每次
hit_count自增后,调用EXPIRE延长 TTL 到 24 小时,保证热点链永不过期。
5.2 PHP 缓存读写逻辑:双写 + 降级保障
修改redirect.php中的查询部分,加入 Redis 读取逻辑。注意:必须有降级机制,当 Redis 不可用时,自动回退到 MySQL 查询,避免雪崩。
// redirect.php 中查询部分替换为: $redisKey = 'short:' . $shortCode; $redis = new Redis(); try { $redis->connect('127.0.0.1', 6379, 1); // 1秒超时 $cacheData = $redis->hGetAll($redisKey); if (!empty($cacheData) && isset($cacheData['status']) && $cacheData['status'] == '0') { // 缓存命中,直接使用 $longUrl = $cacheData['long_url']; $refererWhitelist = json_decode($cacheData['referer_whitelist'] ?? '[]', true) ?: []; // 更新 hit_count 原子操作 $redis->hIncrBy($redisKey, 'hit_count', 1); $redis->expire($redisKey, 86400); // 延长过期时间 } else { // 缓存未命中或状态异常,回退 MySQL $stmt = $pdo->prepare("SELECT long_url, status, referer_whitelist FROM short_urls WHERE short_code = ? AND status = 0"); $stmt->execute([$shortCode]); $row = $stmt->fetch(); if (!$row) { http_response_code(404); exit('Not found'); } $longUrl = $row['long_url']; $refererWhitelist = json_decode($row['referer_whitelist'], true) ?: []; // 写入 Redis 缓存(异步,不阻塞跳转) $redis->multi(); $redis->hMset($redisKey, [ 'long_url' => $longUrl, 'status' => '0', 'hit_count' => '1', 'referer_whitelist' => json_encode($refererWhitelist) ]); $redis->expire($redisKey, 86400); $redis->exec(); } } catch (Exception $e) { // Redis 异常,强制降级到 MySQL error_log("Redis error: " . $e->getMessage()); $stmt = $pdo->prepare("SELECT long_url, status, referer_whitelist FROM short_urls WHERE short_code = ? AND status = 0"); $stmt->execute([$shortCode]); $row = $stmt->fetch(); if (!$row) { http_response_code(404); exit('Not found'); } $longUrl = $row['long_url']; $refererWhitelist = json_decode($row['referer_whitelist'], true) ?: []; }性能对比实测:在 4 核 8G 云服务器上,纯 MySQL 查询 QPS 约 200;加入 Redis 缓存后,QPS 稳定在 4500–5200,CPU 使用率从 95% 降至 25%。关键在于
hIncrBy和expire的原子性,避免了并发更新hit_count的竞态问题。
5.3 缓存一致性保障:MySQL 更新时同步刷新 Redis
光有读缓存不够,当运营后台手动禁用某短链(UPDATE short_urls SET status = 1 WHERE short_code = 'abc123'),Redis 缓存必须立即失效,否则用户仍能跳转。我们用 MySQL 的trigger+UDF或更简单的方案:在 PHP 后台管理接口中,执行 SQL 后,主动删除 Redis Key。
// admin/disable.php $shortCode = $_POST['code']; $pdo->prepare("UPDATE short_urls SET status = 1 WHERE short_code = ?")->execute([$shortCode]); // 同步清理 Redis $redis->del('short:' . $shortCode); // 立即失效提示:不要用
DEL以外的命令(如EXPIRE 1),因为DEL是原子删除,确保缓存与 DB 状态绝对一致。对于高并发禁用场景,可加 Redis 锁(SET lock:short:abc123 1 NX EX 10),但中小项目通常无需。
我上线这套方案后,把原来每周都要手动处理 3–5 次的“红链投诉”,压缩到每月不到 1 次。核心心得只有一条:防红不是对抗平台,而是理解它的规则,然后用可控的基础设施去适配——短码生成只是起点,真正的价值在跳转链路上的每一个可控节点。希望帮到你。
本文还有配套的精品资源,点击获取