首页 / 资讯中心 / 文章详情

短网址生成网站源码实战:Base62发号、302跳转与防红域名轮换

短网址生成网站源码实战:Base62发号、302跳转与防红域名轮换 ★ FEATURED ARTICLE
简介这是一套面向Web开发初学者与进阶者的短网址生成网站源码核心解决长链接缩短与链接防红两大需求适用于社交媒体推广、营销跳转及链接安全防护等场景。源码内置后台管理系统涵盖用户权限管理、长短网址增删改查、访问量来源统计、短码前缀与防红策略配置并集成加密解密、代理转发、混淆算法与动态生成等防红机制同时包含防SQL注入与XSS攻击的安全处理。压缩包共73个文件约1.1MB以21个php业务逻辑文件、20个css与12个js前端资源为主另含字体、图片、svg图标及2个sql数据库脚本结构完整可直接部署。目前已有1250人学习下载。读者可借此理解短网址映射与哈希二次编码原理掌握后台管理系统搭建与Web安全防护思路并在此基础上二次开发API接口、优化防红策略或提升性能是兼具实战与学习价值的完整项目源码。1. 短网址生成网站源码从长链接到短码防红到底在防什么做私域投放、短信触达、社群裂变的人几乎都绕不开一个动作把一条又长又带参数的链接压成https://域名/Ab3xK9这种短码。短网址生成网站源码本质就是一套「长链接进、短码出、访问时 302 跳回原链」的最小 Web 系统核心模块只有三块发号器、映射存储、跳转服务。而标题里反复出现的「防红」说的是另一层现实需求——很多平台会对链接做安全检测命中敏感词、异常域名、高频跳转就会被标红拦截于是从业者会在短链层做域名轮换、路径混淆、落地页中转。这篇不吹概念我按自己搭过的一套 PHP MySQL 方案把发号算法、表结构、跳转逻辑、防红边界和踩过的坑一次讲透新手能照着跑通熟手能看清哪些参数不能乱调。需要先泼一盆冷水短网址本身是中性技术但「防红」不是万能护身符。平台的风控是动态的任何声称「永久防红」的源码都不可信。真正能落地的做法是把短链系统做成可替换域名、可换落地页、可统计点击的基础设施把对抗成本降到「换一个域名继续用」的程度而不是指望某段代码一劳永逸。下面所有内容都围绕这个定位展开。2. 发号器与映射表短码是怎么算出来的短网址系统的技术含量八成集中在「怎么把一条长链接变成一个短且不重复的码」。这一步选错了后面全是坑要么短码越来越长要么并发下撞码要么被爬虫顺着自增 ID 把全站链接扒光。所以先把发号逻辑和存储结构定死再谈跳转和防红。2.1 三种发号方案为什么我最终选 Base62 自增常见做法有三种。第一种是哈希截断把长链接做 MD5 取前 6 位优点是同样的长链得到同样的短码天然去重缺点是必然碰撞6 位十六进制只有 1600 万空间量一大就得处理冲突代码里到处是「查到了就加盐重算」的补丁维护起来很烦。第二种是随机字符串每次随机生成 6 位靠数据库唯一索引兜底实现简单但高并发下插入失败率上升重试逻辑写不好就是死循环。第三种是自增 ID 转 Base62用一个全局自增主键转成0-9a-zA-Z的 62 进制字符串。我一般会选第三种原因是它把「唯一性」交给数据库自增主键保证把「短」交给进制转换保证逻辑最干净。自增 ID 从 1 开始转成 Base62 后 1 位能表示 62 个2 位 3844 个6 位能到 568 亿足够绝大多数业务用很多年。唯一要处理的是「自增 ID 可被枚举」的问题——别人拿到1、2、3就能顺序猜链接。解决办法不是换算法而是在转码前给 ID 做一个可逆混淆比如乘一个大质数再取模或者异或一个固定盐值让短码看起来无规律但解码时还能还原出真实 ID。# Base62 编解码把自增 ID 转成短码也能反解回 ID ALPHABET 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ BASE len(ALPHABET) # 62 def encode(num: int) - str: 自增 ID - Base62 短码 if num 0: return ALPHABET[0] chars [] while num 0: num, rem divmod(num, BASE) chars.append(ALPHABET[rem]) return .join(reversed(chars)) def decode(code: str) - int: 短码 - 自增 ID用于跳转时反查 num 0 for ch in code: num num * BASE ALPHABET.index(ch) return num # 混淆乘质数再取模避免短码连续可猜 PRIME 1000003 MASK 56800235583 # 62^6 - 1保证结果落在 6 位空间内 def obfuscate(num: int) - int: return (num * PRIME) % MASK def deobfuscate(val: int) - int: # 需要 PRIME 在 MASK 下的模逆元这里用扩展欧几里得预先算好 INV pow(PRIME, -1, MASK) return (val * INV) % MASK这段代码的关键点有三个。encode用divmod反复取余时间复杂度是 O(log n)6 位短码最多循环 6 次性能可以忽略。obfuscate里的乘质数取模是仿射变换只要质数与模数互质就是一一映射不会产生碰撞pow(PRIME, -1, MASK)是 Python 3.8 求模逆元的写法能保证解码唯一。参数上MASK取62^6 - 1意味着短码固定 6 位如果你想要更短把 MASK 改成62^5 - 1即可但空间会缩到 9 亿量大的业务要评估。提示混淆用的质数和模数一旦上线就不能改改了所有历史短码全部失效。上线前把这两个值写进配置并备份。2.2 映射表结构一张主表加一张统计表存储层我一般拆成两张表short_link存映射关系link_stat存点击统计。分开的原因是跳转是高频读、统计是高频写混在一张表里会让行锁竞争变严重尤其是 MySQL InnoDB 下每次跳转都UPDATE同一行QPS 上不去。CREATE TABLE short_link ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, short_code VARCHAR(10) NOT NULL COMMENT 短码唯一, long_url VARCHAR(2048) NOT NULL COMMENT 原始长链接, domain_id INT NOT NULL DEFAULT 0 COMMENT 使用的域名编号用于轮换, status TINYINT NOT NULL DEFAULT 1 COMMENT 1正常 0禁用, expire_at DATETIME DEFAULT NULL COMMENT 过期时间NULL 为永久, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_code (short_code), KEY idx_long (long_url(191)) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE link_stat ( code VARCHAR(10) NOT NULL, stat_date DATE NOT NULL, pv INT UNSIGNED NOT NULL DEFAULT 0, uv INT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (code, stat_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;short_code建唯一索引是底线防止任何并发下的重复写入。long_url用VARCHAR(2048)并只对前 191 字符建索引是因为 utf8mb4 下单索引长度有限制全字段索引会报错而 191 前缀足够做「同一长链查重」的过滤。domain_id这个字段是后面防红轮换的关键先留着。link_stat用(code, stat_date)做联合主键写入时用INSERT ... ON DUPLICATE KEY UPDATE pv pv 1把统计做成累加避免每次跳转都读改写。2.3 跳转服务302 还是 301缓存怎么设跳转本身很简单收到短码、查表、返回Location头。但有两个参数必须想清楚。第一是状态码301 是永久重定向浏览器会缓存第二次访问不再请求你的服务器省流量但统计不到302 是临时重定向每次都回源统计准确但服务器压力大。做短链我一般用 302因为点击数据是核心资产省那点流量不值得丢掉统计。第二是缓存短码到长链的映射变化极少可以在应用层加一层本地缓存或 Redis把查库压力挡掉。?php // 跳转入口 index.php伪静态把 /{code} 路由到这里 $code $_GET[code] ?? ; if (!preg_match(/^[0-9a-zA-Z]{4,8}$/, $code)) { http_response_code(404); exit(not found); } $redis new Redis(); $redis-connect(127.0.0.1, 6379); $cacheKey sl: . $code; $longUrl $redis-get($cacheKey); if ($longUrl false) { $pdo new PDO(mysql:host127.0.0.1;dbnameshorturl, user, pass); $stmt $pdo-prepare(SELECT long_url, status, expire_at FROM short_link WHERE short_code ? LIMIT 1); $stmt-execute([$code]); $row $stmt-fetch(PDO::FETCH_ASSOC); if (!$row || $row[status] ! 1) { http_response_code(404); exit(not found); } if ($row[expire_at] strtotime($row[expire_at]) time()) { http_response_code(410); exit(expired); } $longUrl $row[long_url]; $redis-setex($cacheKey, 3600, $longUrl); // 缓存 1 小时 } // 异步写统计避免阻塞跳转 $redis-incr(stat: . $code . : . date(Ymd)); header(Location: . $longUrl, true, 302); exit;这段逻辑里正则先过滤非法短码避免无意义的查库。缓存用setex设 1 小时过期是因为如果某条链接被禁用或过期最多 1 小时后缓存自然失效不会永久跳错。统计没有直接写 MySQL而是incr到 Redis再由定时任务批量落库这样跳转路径上只有一次 Redis 读和一次 Redis 写响应能压到毫秒级。参数上缓存时间可以按业务调链接改动频繁就调短稳定业务可以调到 6 小时甚至更长。3. 防红源码的落地域名轮换、路径混淆与落地页中转「防红」这个词在圈子里被用得很玄拆开看其实是三件事让检测系统不容易判定你的链接是营销/风险链接、让链接被封后能快速换血、让真实落地页不直接暴露。这一章讲具体怎么做但先把边界说清楚——这些手段只能降低被拦截的概率不能保证不被拦平台规则变化时该封还是封。3.1 域名池轮换一个短码对应多个入口域名最有效的防红手段不是改代码是准备多个域名。同一个短码可以在不同域名下都能访问比如a1.com/Ab3xK9和b2.com/Ab3xK9指向同一条长链。当a1.com被标红投放时换成b2.com即可短码和统计都不用动。实现上就是在short_link之外加一张域名表跳转时根据当前请求的 Host 判断用哪个域名生成短链时按权重随机分配。CREATE TABLE short_domain ( id INT NOT NULL AUTO_INCREMENT, host VARCHAR(128) NOT NULL COMMENT 入口域名, weight INT NOT NULL DEFAULT 100 COMMENT 分配权重, status TINYINT NOT NULL DEFAULT 1 COMMENT 1启用 0停用, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_host (host) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;生成短链时从status1的域名里按weight做加权随机拼成完整短链返回给调用方。跳转时不需要关心是哪个域名进来的因为短码全局唯一直接查short_link就行。这里有个容易翻车的点如果多个域名共用一套 Cookie 或 Session跨域会出问题所以跳转服务本身不要依赖登录态纯无状态最好。注意域名池不是越多越好。每个域名都要备案、要配证书、要维护解析管理成本很高。我一般维持 3 到 5 个可用域名轮换被拦一个补一个比一次性铺 20 个然后全部失管要靠谱。3.2 路径混淆与参数透传别让长链裸奔很多平台检测的是最终落地页的 URL如果你的短链 302 直接跳到一条带明显营销参数的地址检测系统顺着跳一次就看到了。常见做法是在中间加一层中转页短链先跳到一个自己控制的页面页面里再用 JS 或 meta refresh 跳真实地址同时把原始参数透传过去。这样检测系统看到的第一跳是你自己的域名真实落地页藏在第二跳。// 中转页 redirect.html短链 302 到这里再由前端跳真实地址 (function () { // 从 URL 里取出真实目标做一次白名单校验防止被当成开放重定向 var params new URLSearchParams(window.location.search); var target params.get(t); var allowHosts [your-landing.com, cdn.your-landing.com]; function isAllowed(url) { try { var u new URL(url); return allowHosts.indexOf(u.hostname) ! -1; } catch (e) { return false; } } if (target isAllowed(decodeURIComponent(target))) { // 透传其余业务参数 var extra params.get(p) || ; var finalUrl decodeURIComponent(target) (extra ? ? extra : ); window.location.replace(finalUrl); } else { document.body.innerHTML p链接无效/p; } })();这段代码的核心是白名单校验。中转页如果不校验目标域名就成了开放重定向漏洞别人可以拿你的域名跳任何站轻则被举报重则域名被拉黑。allowHosts里只放你自己的落地页域名t参数是编码后的真实地址p是业务参数。用window.location.replace而不是href是为了不在地理历史里留下中转页用户点返回直接回上一页。3.3 落地页与短链分离部署一个血泪经验短链服务和落地页千万不要放在同一台服务器、同一个 IP 段。短链域名被拦时如果落地页同 IP很可能一起被牵连。我一般把短链跳转部署在一台低配机器上落地页放在另一台甚至另一个云厂商两者只通过 URL 参数通信。这样短链域名被封换域名即可落地页不受影响落地页要改版也不影响短链统计。部署上短链服务用 Nginx PHP-FPM 就够伪静态规则把/{code}转发到入口文件# nginx 配置片段 server { listen 80; server_name a1.com b2.com; root /var/www/shorturl; index index.php; location / { try_files $uri $uri/ /index.php?code$uri$args; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }try_files把不存在的路径都交给index.php并把路径作为code参数传入这样a1.com/Ab3xK9就能被正确解析。server_name同时写多个域名方便域名池共用一套配置。参数上fastcgi_pass指向 PHP-FPM 的监听地址如果用的是 Unix socket 要改成unix:/run/php-fpm.sock。4. 避坑与排查短链上线后最容易翻车的五件事这套系统我前后部署过几次每次都会踩到类似的坑。下面五条按「现象 → 原因 → 解决」写都是真实遇到过的照着排查能省不少时间。短码突然 404但数据库里明明有记录。现象是部分短码访问不了查库存在且status1。原因通常是 Redis 缓存里存了旧的空值或错误值比如某次查询时链接还没生效缓存了空字符串之后一直命中缓存。解决是缓存空值时也要设短过期时间或者干脆不缓存空结果同时在禁用/删除链接时主动del对应的缓存 key。并发下出现重复短码唯一索引报错。现象是高峰期日志里大量Duplicate entry。原因是发号用了「先查再插」的逻辑两个请求同时查到同一个 ID。解决是把发号交给数据库自增主键插入时只写long_url让数据库生成id再用LAST_INSERT_ID()拿到 ID 转短码最后UPDATE回写short_code整个过程没有「查了再插」的窗口。跳转被浏览器缓存改了目标地址不生效。现象是后台把长链改了用户访问还是跳旧地址。原因是用了 301 或者给跳转响应加了强缓存头。解决是坚持用 302并且在响应里加Cache-Control: no-store确保每次跳转都回源。统计数字对不上PV 明显偏低。现象是 Redis 里统计的点击和实际访问量差很多。原因通常是跳转前就exit了统计代码没执行到或者异步落库的定时任务挂了没发现。解决是把统计写在header之前并且给定时任务加监控落库失败要告警。域名被拦后换域名老短链全部失效。现象是换了新域名但之前发出去的短链还是指向老域名用户点开就是拦截页。原因是生成短链时把域名写死在返回结果里了。解决是短码和域名解耦短码全局唯一任何域名都能解析同一个短码换域名时只需要在投放侧替换域名前缀短码部分不变。5. 进阶把短链系统做成可观测、可灰度的小基础设施走到这一步短链已经能跑了但离「敢放心投钱」还差一层——你得知道每条链接的真实表现能在出问题时快速定位能小范围试错而不是一把梭。这一章讲三个我实际在用的技巧都是围绕「可观测」和「灰度」展开的。第一个是给跳转加请求指纹。在跳转入口记录code、referer、user_agent、ip的哈希写入 Redis 的 HyperLogLog 做 UV 估算成本极低但能看出异常。比如某条短码突然 UV 暴涨但 PV 不变大概率是被爬虫扫了某条链接 referer 全是空可能是被直接粘贴到某些客户端里。这些信号比单纯看 PV 有用得多。第二个是灰度发布新域名。新域名上线不要直接全量切先给它 5% 的权重观察一周内有没有被拦截的反馈再逐步加权重。实现就是在加权随机里给新域名一个低weight同时单独统计这个域名下的跳转成功率。成功率跌破阈值就自动把status置 0避免影响大盘。第三个是落地页健康检查。短链最终要跳到落地页如果落地页挂了短链跳过去也是白屏。我一般写一个定时任务每隔几分钟对allowHosts里的域名发一个 HEAD 请求状态码非 200 就告警。这个检查很土但比出事之后被用户投诉要强。# 落地页健康检查配合 crontab 每 5 分钟跑一次 import requests HOSTS [https://your-landing.com, https://cdn.your-landing.com] TIMEOUT 5 def check(host): try: r requests.head(host, timeoutTIMEOUT, allow_redirectsTrue) return r.status_code 200 except requests.RequestException as e: return False if __name__ __main__: for h in HOSTS: ok check(h) print(f{h} - {OK if ok else FAIL}) # 实际使用时这里接告警比如钉钉/飞书 webhookrequests.head只取响应头不下载页面内容开销很小。allow_redirectsTrue是因为落地页可能有 CDN 跳转要跟到最终地址。TIMEOUT设 5 秒避免慢响应把检查任务拖死。这段脚本本身没什么技术含量但它是整个系统里最容易被忽略、出事时又最救命的一环。最后说个我自己的习惯每次上线新的短链域名或改动跳转逻辑我都会先用一条测试短链自己点一遍看跳转、看统计、看缓存确认无误再放量。这个动作花不了两分钟但帮我挡掉过好几次「配置写错导致全量 404」的事故。短链这东西看着简单真正跑起来全是细节希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站