短链接应采用“id自增+62进制转换”方案:用数据库自增id(如12345)转为62进制短码(如"3dr"),确保唯一、可逆、无碰撞;需建表含id、long_url、created_at,加long_url唯一索引,用insert...on duplicate key update防重复;跳转时只从path_info取短码,校验合法性后302跳转,强制header+exit,关闭display_errors并捕获所有异常。

短链接生成不能靠 md5() 或 uniqid() 直接截取
直接对原始 URL 做 md5() 然后取前 6 位,看似简单,但碰撞风险高、不可逆、不支持自定义和统计。而 uniqid() 依赖时间戳,高并发下易重复,且无法反查原链接。
真正可用的方案是「ID 自增 + 进制转换」:用数据库自增主键(如 id = 12345)转成 62 进制(0–9a–zA–Z),得到短码(如 "3dR")。这样天然唯一、可逆、无碰撞、易扩展。
- 建表至少包含:
id(AUTO_INCREMENT)、long_url(TEXT)、created_at - 转换函数别手写循环除法,用现成的:
base_convert()只支持到 36 进制,得自己实现 62 进制编码(含大小写字母) - 注意 MySQL 的
id起始值——如果从 1 开始,1编码后是"1",太短;可设AUTO_INCREMENT = 1000避免单字符
跳转逻辑必须用 header() + exit,且要防 Open Redirect
用户访问 /go/3dR 时,PHP 查出对应 long_url,然后 302 跳转。但若不做校验,攻击者可构造 /go/xxx?to=https://evil.com 绕过数据库直跳——这不是短链,是开放重定向漏洞。
- 只允许从路径段(PATH_INFO)提取短码,拒绝任何查询参数参与跳转逻辑
- 查库失败或
long_url为空/非法(如不含http且非内网地址),直接返回 404,不要 fallback 到默认页 - 跳转前强制加
header('Location: ' . $url); exit;——漏掉exit会导致后续代码继续执行,可能泄露信息或重复写日志 - 避免用
http_response_code(302)单独设状态码,header('Location: ...')会自动发 302,再设反而容易冲突
高并发下 INSERT ... ON DUPLICATE KEY UPDATE 是唯一靠谱去重方式
短链服务最常被忽略的点:同一长链接多次提交,不该生成多个短码。但用“先 SELECT 再 INSERT”在并发下必然产生重复记录。
立即学习“PHP免费学习笔记(深入)”;
- 给
long_url加唯一索引(可用前缀索引,如long_url(255),避免全文索引开销) - 插入时用:
INSERT INTO short_urls (long_url) VALUES (?) ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id) - 然后用
mysqli_insert_id()或PDO::lastInsertId()拿到真实 ID(即使命中 duplicate,它也返回已存在行的id) - 别依赖
REPLACE INTO:它本质是 DELETE + INSERT,在有外键或触发器时行为不可控
上线前必须关掉 display_errors 并捕获所有数据库异常
短链接口是高频入口,任何未捕获的 PDOException 或 mysqli 错误,一旦 display_errors = On,就会把数据库结构、路径甚至密码(如果错配在 DSN 里)直接打到响应体中。
- 确保
php.ini中display_errors = Off,且代码里没调用ini_set('display_errors', '1') - 所有 DB 查询必须 try/catch,失败时统一返回 500 或 400,日志记详细上下文(短码、原始 URL、时间),但绝不暴露 SQL 或堆栈
- 特别注意:
base62_decode()如果传入非法字符(如空格、中文、"_"),应提前过滤或报 400,而不是让后续 intval() 返回 0 导致查出第 0 条记录
短链的核心不是“怎么压缩字符串”,而是“怎么让每次生成和跳转都原子、可追溯、不泄密”。ID 映射这条路看起来笨,但压测到几千 QPS 也没出过问题——花哨算法反而容易在边界 case 上翻车。











