ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

短网址生成与防红源码实战:域名池调度、跳转链路分层与边缘解析优化

短网址生成与防红源码实战:域名池调度、跳转链路分层与边缘解析优化 简介这是一套带后台管理系统的短网址生成与防红服务源码面向有一定PHP基础的Web开发者、建站爱好者及需要链接推广与防封场景的运营人员。它通过哈希映射与自定义短码将长网址转为易记短链并借助加密混淆、代理转发等策略降低链接被平台屏蔽的风险。压缩包共73个文件约1.1MB以21个php业务脚本、20个css与12个js前端资源为主另含6个ttf字体、4个png与1个jpg图片、2个sql建表文件及svg、eot、woff等图标字体资源结构完整可直接部署。后台涵盖用户权限管理、网址增删改与短链生成、访问量来源统计、短码前缀与防红策略配置并内置防SQL注入、XSS等安全处理。目前已有1249人学习下载适合作为短链算法、Web安全与后台系统开发的实践项目也可在此基础上二次开发API接口、优化防红策略或提升性能。1. 短网址生成网站源码与防红源码从域名池到落地页的完整工程链路做短网址生成网站源码的人十个里有八个最后都会撞上同一个问题链接在微信、QQ、抖音里点开是白屏或者拦截页。这不是代码写得烂而是短网址这个形态天然会被风控盯上——域名被拉黑、路径被特征识别、跳转链路被中间页截断。所谓防红源码本质上不是让链接“隐身”而是通过域名轮换、落地页伪装、跳转链路分层把单一入口的风险摊薄到多个可替换的节点上。这套方案适合谁适合手里有多个备案或未备案域名、想自己搭一套可控短链系统、并且需要链接在主流社交场景里稳定打开的中小团队和个人站长。核心要解决三件事短码怎么生成不冲突、域名池怎么调度不被一锅端、跳转链路怎么设计让风控抓不到固定特征。下面按工程落地顺序拆开讲。2. 短码生成与存储选型为什么自增ID转62进制是坑雪花ID加布隆过滤才是正解2.1 短码生成的三种常见做法与翻车点短网址生成网站源码里最核心的一行逻辑就是短码怎么来。我见过最多的写法是数据库自增ID转62进制代码短、好理解但上线就翻车。原因有两个第一自增ID暴露业务量竞品看一眼短码就知道你一天生成多少条第二分库分表之后自增ID不唯一迁移数据时短码直接冲突。第二种做法是随机字符串比如取6位大小写字母加数字碰撞概率看着低但量级上到千万级之后每次插入都要查重数据库压力陡增。第三种是我现在用的方案雪花IDSnowflake生成全局唯一长整型再转62进制压缩成短码配合布隆过滤器做前置去重判断。雪花ID的结构是41位时间戳加10位机器ID加12位序列号单机每毫秒能出4096个不重复ID。转62进制之后一个19位的长整型会变成大约11位的短码。如果你觉得11位太长可以截取雪花ID的低位部分再转但截取位数要算清楚碰撞概率。我一般截取后52位转出来是9位短码在千万级数据量下碰撞率低于百万分之一。import time class Snowflake: def __init__(self, worker_id1, datacenter_id1): self.worker_id worker_id self.datacenter_id datacenter_id self.sequence 0 self.last_timestamp -1 # 起始时间戳2024-01-01 self.epoch 1704067200000 def _current_ms(self): return int(time.time() * 1000) def next_id(self): ts self._current_ms() if ts self.last_timestamp: self.sequence (self.sequence 1) 0xFFF if self.sequence 0: # 当前毫秒序列号用尽等下一毫秒 while ts self.last_timestamp: ts self._current_ms() else: self.sequence 0 self.last_timestamp ts # 41位时间戳 | 5位数据中心 | 5位机器 | 12位序列 return ((ts - self.epoch) 22) | (self.datacenter_id 17) | (self.worker_id 12) | self.sequence def to_base62(num): chars 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ if num 0: return chars[0] result [] while num 0: num, rem divmod(num, 62) result.append(chars[rem]) return .join(reversed(result)) sf Snowflake(worker_id1, datacenter_id1) short_code to_base62(sf.next_id())[:9] print(short_code)这段代码里epoch是自定义起始时间设成项目启动那天就行越晚设可用年限越长。worker_id和datacenter_id在多机部署时必须唯一否则同一毫秒不同机器会出重复ID。to_base62之后截取前9位截取位数越少碰撞概率越高9位在千万级数据量下是安全线。布隆过滤器用Redis的Bitmap实现每次生成短码前先查一下存在就重新生成不存在就写入并落库。2.2 存储引擎选型MySQL加Redis缓存别上MongoDB短网址系统的读写比大概是1比1000写少读多而且读要求极低延迟。我试过用MongoDB存短码映射查询是快但数据量上到五千万之后索引膨胀得厉害内存吃不消。后来换回MySQL加RedisMySQL只存短码和原始URL的映射关系Redis做一级缓存热点链接直接命中内存。表结构就三个字段短码做主键、原始URL、创建时间。原始URL字段用TEXT类型别用VARCHAR(255)有些带参数的推广链接能超过两千字符。CREATE TABLE short_url ( code varchar(16) NOT NULL, url text NOT NULL, created_at int unsigned NOT NULL, PRIMARY KEY (code), KEY idx_created (created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时code做主键而不是自增ID因为查询永远走短码主键就是短码能省一次回表。created_at加索引是为了做数据清理和统计。Redis缓存用SETEX设置过期时间我一般设24小时热点链接过期后回源查MySQL再写回缓存。注意别用KEYS *去扫缓存用SCAN游标分批处理否则Redis会阻塞。提示布隆过滤器的误判率设0.01%就够用再低会浪费大量内存。Redis Bitmap的容量按最大短码数量乘以10来算留足余量。3. 防红源码的域名池调度把单点风险拆成可替换的节点矩阵3.1 域名池的三种调度策略与适用场景防红源码的核心不是加密跳转而是域名调度。你只有一个域名被拉黑就是全站瘫痪你有二十个域名拉黑三个还剩十七个。域名池的调度策略我用过三种轮询、权重、随机。轮询最简单每个域名按顺序轮流用适合域名质量都差不多的情况。权重适合域名有优劣之分比如老域名权重高、新域名权重低按权重分配流量能延长老域名寿命。随机是我现在主要用的因为轮询和权重都有固定规律风控系统抓一段时间就能摸出你的调度节奏随机打散之后没有明显规律。域名池的数据结构用Redis的Hash存每个域名一个字段值是该域名的当前状态和权重。状态分三种正常、限流、封禁。限流是域名还能用但已经出现拦截迹象比如微信打开提示“已停止访问”这时候降低权重但不完全停用。封禁是彻底打不开直接从池子里摘掉。import redis import random r redis.Redis(host127.0.0.1, port6379, db0) def add_domain(domain, weight100): r.hset(domain_pool, domain, fnormal:{weight}) def pick_domain(): pool r.hgetall(domain_pool) candidates [] weights [] for domain, info in pool.items(): status, weight info.decode().split(:) if status normal: candidates.append(domain.decode()) weights.append(int(weight)) elif status limited: # 限流域名权重降到十分之一 candidates.append(domain.decode()) weights.append(int(weight) // 10) if not candidates: raise Exception(域名池为空) return random.choices(candidates, weightsweights, k1)[0] def mark_domain(domain, status): info r.hget(domain_pool, domain) if info: _, weight info.decode().split(:) r.hset(domain_pool, domain, f{status}:{weight})add_domain往池子里加域名初始权重设100。pick_domain用random.choices按权重随机选限流域名权重自动降到十分之一既保留可用性又减少曝光。mark_domain用来更新状态封禁域名直接设成banned下次调度就不会被选中。这套逻辑跑起来之后单个域名被封对整体可用性的影响可以控制在5%以内。3.2 落地页伪装中间页不是随便放个HTML就行防红源码里另一个关键点是落地页。短链直接跳原始URL风控一眼就能看出是跳转中间加一个落地页把跳转逻辑藏在JavaScript里风控抓不到固定特征。但落地页不能随便写我踩过的坑是落地页里直接写window.location.href 原始URL风控引擎解析JS之后照样拦截。后来改成动态加载原始URL通过接口获取落地页本身不含任何目标地址。// 落地页逻辑从接口拿真实地址再跳转 async function redirect() { const code new URLSearchParams(window.location.search).get(c); if (!code) { document.body.innerHTML p页面不存在/p; return; } try { const resp await fetch(/api/resolve?code code, { method: GET, headers: { X-Requested-With: XMLHttpRequest } }); const data await resp.json(); if (data.url) { // 延迟200毫秒再跳模拟用户阅读行为 setTimeout(() { window.location.replace(data.url); }, 200); } else { document.body.innerHTML p链接已失效/p; } } catch (e) { document.body.innerHTML p网络异常请稍后重试/p; } } redirect();这段代码里code参数从URL的query string取接口/api/resolve返回真实地址。setTimeout延迟200毫秒是为了模拟用户点击后阅读页面的行为风控系统会检测跳转速度0毫秒跳转的特征太明显。window.location.replace比href更干净不会在浏览器历史里留下中间页记录。落地页的HTML里不要出现“跳转中”“即将离开”这类文案用“页面加载中”或者干脆空白页加一个loading图标。注意落地页的域名要和短链域名分开短链域名被拉黑时落地页域名还能继续用。落地页域名建议用大厂备案域名权重高、不容易被误伤。4. 跳转链路分层与特征消除让风控抓不到固定模式4.1 三层跳转链路的设计与参数传递防红源码的跳转链路我一般设计成三层短链域名到落地页域名落地页域名到接口域名接口域名返回真实URL。三层域名互相独立任何一层被拉黑换掉那一层就行不影响其他层。参数传递用短码加签名的方式短码是62进制字符串签名是短码加盐之后的MD5前8位。接口收到请求先验签签名不对直接返回404防止别人扫你的接口。import hashlib SALT your_secret_salt_here def make_sign(code): raw code SALT return hashlib.md5(raw.encode()).hexdigest()[:8] def verify_sign(code, sign): return make_sign(code) sign # 生成带签名的短链 def build_short_url(domain, code): sign make_sign(code) return fhttps://{domain}/r?c{code}s{sign}SALT是服务端密钥不要写在前端代码里。make_sign取MD5前8位够用且短。build_short_url生成的链接里带c和s两个参数落地页拿到之后先调接口验签验签通过再返回真实URL。这套机制能挡住大部分扫描器因为扫描器不知道你的SALT构造不出合法签名。4.2 特征消除User-Agent、Referer和跳转速度的处理风控系统判断一个链接是不是恶意跳转主要看三个特征User-Agent是不是常见浏览器、Referer是不是来自社交平台、跳转速度是不是过快。User-Agent不用改保持正常就行改了反而可疑。Referer在落地页里拿不到因为跨域请求默认不带Referer这反而是好事。跳转速度前面说了延迟200毫秒。还有一个细节是落地页的HTTP状态码返回200就行别返回302302跳转的特征太明显。# Nginx配置落地页返回200接口返回JSON location /r { # 落地页返回200 add_header Content-Type text/html; return 200 !DOCTYPE htmlhtml.../html; } location /api/resolve { # 接口返回JSON add_header Content-Type application/json; proxy_pass http://127.0.0.1:8000; }Nginx这层做两个事情落地页路径/r直接返回HTML内容状态码200接口路径/api/resolve反代到后端服务。注意add_header要写在return之前否则不生效。落地页的HTML内容可以存在Nginx的配置里也可以从文件读我一般放文件里方便改。提示接口返回的JSON里不要带原始URL的域名只返回完整URL字符串让前端自己解析。这样即使接口被逆向也拿不到你的域名列表。5. 避坑与排查短网址防红系统上线后最容易翻车的五个地方5.1 短码碰撞导致跳转到错误页面现象用户反馈短链打开之后跳到了别人的链接。原因短码生成时没有做唯一性校验或者布隆过滤器误判导致重复短码落库。解决每次生成短码后先查Redis布隆过滤器存在就重新生成落库时用INSERT IGNORE如果短码已存在会静默失败捕获异常后重新生成。布隆过滤器的误判率设0.01%误判时最多多生成一次不影响性能。5.2 域名池调度不均导致单个域名被集中封禁现象域名池里某个域名突然被封其他域名没事。原因随机调度在样本量小的时候分布不均匀某个域名短时间内被大量使用触发风控阈值。解决在随机的基础上加一个滑动窗口计数器每个域名每分钟最多分配N次N根据域名质量设新域名设50老域名设200。超过阈值就跳过该域名用下一个。5.3 落地页被识别为跳转中间页现象落地页在微信里打开直接白屏没有任何提示。原因落地页的HTML结构太像跳转页比如只有一个script标签、body里没有内容、或者title是“跳转中”。解决落地页加一个正常的页面结构header、footer、一段说明文字跳转逻辑放在window.onload之后执行。title用“页面加载中”或者干脆用域名。5.4 接口被扫描导致真实URL泄露现象有人写脚本批量请求/api/resolve拿到了大量真实URL。原因接口没有做频率限制和签名校验。解决接口加签名校验签名不对返回404加IP频率限制单IP每分钟最多请求30次超过返回429接口返回的URL做一次短时token包装token有效期60秒过期后需要重新请求。5.5 数据库连接数被打满现象短链服务运行一段时间后响应变慢MySQL报Too many connections。原因每次请求都新建数据库连接没有用连接池。解决用连接池Python里用DBUtils的PooledDBJava里用HikariCP。连接池大小设成最大并发数的1.5倍我一般设50。Redis连接也用连接池别每次new Redis()。注意排查问题时先看Nginx的access log确认请求打到了哪个路径、返回了什么状态码。再看应用日志确认短码解析和域名调度是否正常。最后看MySQL和Redis的慢查询定位性能瓶颈。6. 进阶技巧用边缘节点做短链解析把响应压到50毫秒以内短网址系统的响应速度直接影响用户体验和风控评分。我现在的做法是把短码解析逻辑下沉到边缘节点用Cloudflare Workers或者阿里云EdgeRoutine在离用户最近的节点完成短码到URL的映射查询。边缘节点缓存热点短码命中缓存直接返回302不命中再回源到中心数据库。这样平均响应时间从200毫秒压到50毫秒以内而且边缘节点天然分散风控系统很难通过单一IP的请求频率来判断异常。具体实现上边缘节点的KV存储里存短码和URL的映射TTL设1小时。回源接口用前面说的签名机制边缘节点拿到URL之后缓存到本地KV下次同一个短码直接命中。边缘节点的代码逻辑很简单// Cloudflare Workers 边缘解析逻辑 addEventListener(fetch, event { event.respondWith(handleRequest(event.request)); }); async function handleRequest(request) { const url new URL(request.url); const code url.searchParams.get(c); if (!code) { return new Response(Not Found, { status: 404 }); } // 先查边缘KV缓存 let target await SHORT_URL_KV.get(code); if (!target) { // 缓存未命中回源查询 const resp await fetch(https://api.yourdomain.com/resolve?code code, { headers: { X-Edge-Auth: your_edge_token } }); if (!resp.ok) { return new Response(Link Expired, { status: 404 }); } const data await resp.json(); target data.url; // 写入边缘缓存TTL 3600秒 await SHORT_URL_KV.put(code, target, { expirationTtl: 3600 }); } // 302跳转 return Response.redirect(target, 302); }SHORT_URL_KV是边缘节点的KV命名空间expirationTtl设3600秒。回源请求带X-Edge-Auth头做鉴权防止别人直接调你的回源接口。Response.redirect返回302状态码干净。这套方案的成本很低Cloudflare Workers免费额度每天10万次请求小规模短链系统完全够用。验证方法很简单用curl测响应时间curl -o /dev/null -s -w %{time_total}\n https://yourdomain.com/r?cabc123连续测十次取平均值。如果超过100毫秒检查边缘节点缓存命中率命中率低于80%就调大TTL或者预热热点短码。我一般会在每天凌晨跑一个脚本把前一天访问量前1000的短码批量写入边缘KV保证热点链接始终命中缓存。最后说一个血泪教训别在短链系统里存任何用户敏感信息短码和URL的映射关系就是全部数据。我见过有人在短链系统里加用户ID、设备指纹、地理位置结果数据库被拖之后泄露了一大片。短链就是短链只做跳转别做用户画像。希望帮到你。本文还有配套的精品资源点击获取
返回列表