简介:黑色简洁的PHP短网址/短链接生成源码是一套可直接部署的完整项目,面向需要自建短链服务的站长、网络爱好者与PHP开发者,可解决长链接冗长难记、外链地址分散、访问效果无法统计等问题。前端提供简洁优雅的响应式设计,支持创建普通/自定义短链、设置密码保护、生成小书签、链接统计以及暗色主题,后端则支持网址管理、网站设置、广告添加与编辑、访问分析及自定义CSS。资源包共一百零三个文件,约681KB,以三十四个PHP核心业务文件为主,辅以SCSS/LESS/CSS样式、JavaScript交互脚本、TTF/OTF等字体图标资源及两个SQL数据库文件,结构清晰便于二次开发。已有二百一十二人学习或浏览,适合有一定PHP基础、希望快速搭建带广告位和统计分析功能的短链平台的开发者参考。
1. 自己搭一个 PHP 短网址服务:比第三方平台多出来的那点掌控感
做运营或者搞推广的人,大概率都遇到过这种场景:抖音主页挂的链接带一长串参数,微信里发个淘宝口令被折叠得不成样子,或者线下物料上印了一个超过三十个字符的链接,扫码的人瞬间没了耐心。短网址工具不少,但用第三方平台总有几个膈应的地方——链接有效期说不准、后台统计不够细、域名被别人捏在手里说停就停。这份黑色简洁的 PHP 短网址短链接生成源码,就是把短链接服务整个搬到自己服务器上,数据库、生成规则、跳转逻辑、点击统计全部自己说了算。源码是纯 PHP 写的,不依赖框架,虚拟主机也能跑,适合想把外链管理权握在自己手里的人,也适合 PHP 学习者拿它当完整的项目练手。
2. 短网址是怎么变短的:哈希、进制与跳转逻辑一次讲透
2.1 短网址的两种主流生成思路:自增ID与哈希截取
短网址的核心不是“压缩”,而是“映射”。原始链接可能是一百个字符,但短网址系统只需要维护一张表:短码 → 原链接。用户访问短码时,服务器查表找到原链接,然后 301 或 302 跳转过去。所以短码本身只是原链接在数据库里的一个主键或者唯一索引的另一种表示。
常见的生成思路有两种。第一种是自增 ID 接码:数据库里每插入一条新记录,主键自动加一,然后用进制转换把这个整数变成短码。优点是绝对不会重复,缺点是短码有规律,别人可以通过递增访问把你的短链接遍历完。第二种是哈希截取:对原链接做一次哈希(MD5、SHA1 都行),取前 6 到 8 位作为短码。优点是无状态,不依赖数据库自增也能算出来,缺点是存在碰撞风险,需要查重。
这份源码采用的是自增 ID + 62 进制转换的方案,也就是先把链接存进表拿到自增 ID,再把这个 ID 转成由数字、大小写字母组成的短码。它把“短码不可猜测”这件事放到了一边,优先保证的是短码的稳定和绝对唯一。实际运营中短码被遍历抓取的风险确实存在,但从流量转化角度看,大多数短网址服务的核心诉求是短和稳,不是加密。
2.2 62 进制换算:从十进制到大小写字母的完整映射
62 进制就是用了 10 个数字 + 26 个小写字母 + 26 个大写字母,总共 62 个字符来代表一个整数。十进制 0 对应字符 0,十进制 61 对应字符 Z,十进制 62 对应字符 10。换算过程和我们熟悉的十进制转二进制完全一样,只是把除数从 2 换成了 62。
function dec2any($num, $base = 62) { $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $result = ''; while ($num > 0) { $result = $chars[$num % $base] . $result; $num = intval($num / $base); } return $result === '' ? '0' : $result; } function any2dec($str, $base = 62) { $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $len = strlen($str); $result = 0; for ($i = 0; $i < $len; $i++) { $pos = strpos($chars, $str[$i]); if ($pos === false) return false; // 非法字符直接拒绝 $result = $result * $base + $pos; } return $result; }dec2any 是把十进制 ID 转成短码的核心,any2dec 是反推。有一个细节要注意:字符映射表的顺序不要随便改。有的开发者会把大小写字母顺序调换,导致数据库中已有的短码全部失效。部署这套源码之后,如果后续要自己做二次开发,这两个函数的映射表必须保持和原系统一致。另外,$base 参数不要传 10 以内的小进制,否则短码长度会失控。
2.3 跳转的本质:301 与 302 的选择,以及统计埋点
短网址服务的跳转有两条路:301 永久重定向和 302 临时重定向。301 会让浏览器和搜索引擎缓存跳转结果,第二次访问时直接跳转,服务器压力小,但点击统计会丢失。302 每次都会请求服务器,统计能记下来,代价是响应多一次。做推广落地页的短链服务,我一般会选 302,因为我们需要知道每个链接的真实点击量和来源,301 缓存之后数据会少一大截。
源码里默认用的是 302 跳转,同时还做了 UA 判断和 IP 记录。跳转 PHP 文件里先查库拿到原链接和状态,然后记录点击日志,最后执行 header() 跳转。这里有一个容易被忽视的细节:header('Location: xxx') 之前不能有任何输出,包括 HTML 空白和 BOM 头,否则 PHP 会报“headers already sent”错误。
3. 把源码跑起来:本地与服务器的完整部署流程
3.1 源码包结构说明与运行环境检查
拿到这份“黑色简洁的 PHP 短网址短链接生成源码.rar”,解压之后目录结构并不复杂。入口文件是 index.php,负责接收短码并跳转;admin 目录是后台管理界面;api 目录提供生成短链接的接口;install 目录放数据库初始化脚本。整体是原生 PHP 写的,没有用到 Composer 依赖,所以部署门槛很低。
环境要求方面,常见做法是 PHP 5.6 以上 + MySQL 5.5 以上 + Apache 或 Nginx。需要注意 PHP 版本不要太高,有些低版本的代码在 PHP 8.0 之后会报 Deprecated 警告,比如隐式 nullable 参数声明。如果服务器上跑的是 PHP 8.1 或更高版本,建议装个 WordPress 用的那种兼容层插件,或者直接搜一下源码里的 ereg、mysql_ 这类老函数,把它们替换成对应的新写法。
检查环境最快的方式是看 phpinfo():确认 PDO 扩展和 pdo_mysql 驱动已启用,确认短标签 short_open_tag 是 On,确认伪静态依赖的 mod_rewrite(Apache)或 rewrite 模块(Nginx)已开启。这三个里缺任何一个,项目都会以不同的姿势挂掉。
3.2 数据库导入与配置文件修改
源码包里通常会带一个.sql 文件,文件名一般是 short_url.sql 或者 install.sql。用 phpMyAdmin 或命令行导入即可。表结构核心就两张:urls 表存短码和原链接,logs 表存点击日志。urls 表的短码字段要加唯一索引,不然并发插入时容易出现重复数据。
mysql -u root -p short_url < short_url.sql导入之后打开 config.php(有的版本叫 db.php 或 conn.php),把数据库连接参数改成你自己的。这个文件一般在根目录或 inc 目录下。里面需要改的是 DB_HOST、DB_NAME、DB_USER、DB_PASS 这四个常量。改完之后先用简单的 PHP 脚本测一下连接:
<?php require 'config.php'; try { $pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PASS); echo '数据库连接正常'; } catch (PDOException $e) { echo '连接失败: ' . $e->getMessage(); }这里有几个容易踩的雷:一是数据库账号没有远程访问权限,二是字符集没设为 utf8mb4,三是 MySQL 8.0 默认的认证插件是 caching_sha2_password,老版本 PHP 的 PDO 可能连不上,需要在 MySQL 里把用户改成 mysql_native_password。这三个问题在 Windows 本地跑的时候尤其常见。
3.3 Apache 与 Nginx 的伪静态规则配置
短网址系统的核心要求是:访问 /abc123 这样的路径时,不经过任何 .php 文件名的路由,直接由 index.php 接收参数。Apache 环境下一般是源码包自带 .htaccess 文件,内容大概长这样:
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?code=$1 [QSA,L]这套规则的意思很直接:如果请求的不是一个真实文件,也不是一个真实目录,就把整条路径作为 code 参数传给 index.php。如果你访问短链接得到 404,大概率就是 .htaccess 没生效。查两个地方:Apache 的 AllowOverride 是不是 None,以及 httpd.conf 里有没有加载 rewrite_module。
Nginx 环境没有 .htaccess,要在站点配置里加一段。很多第一次部署的人不知道这段怎么写,其实就三行:
location / { if (!-e $request_filename) { rewrite ^/(.*)$ /index.php?code=$1 last; } }注意 Nginx 的 rewrite 和 Apache 的 RewriteRule 有一个行为差异:Apache 把路径作为 code 参数时会自动 URL 解码,Nginx 默认会保留原始编码后的值。如果短码里带特殊字符,收到参数后先做一次 urldecode 比较稳妥。我一般会在 index.php 跳转逻辑的最前面加一行 $code = urldecode($code),这样两边行为就一致了。
4. 核心代码拆解:生成、跳转、统计三块黑匣子
4.1 生成短码的核心函数
源码里生成短码的逻辑集中在 api/create.php 或 admin/add.php 里。核心流程是:接收原链接 → 检查是否已存在 → 插入数据库 → 拿到自增 ID → 转成短码 → 更新该条记录的短码字段。后两步有些版本是先转完短码再插入,本质上一样,区别在于并发下的行为。
function createShortUrl($pdo, $longUrl) { // 先查一遍,避免同一条链接生成多个短码 $stmt = $pdo->prepare("SELECT code FROM urls WHERE long_url = ? LIMIT 1"); $stmt->execute([$longUrl]); $row = $stmt->fetch(); if ($row) { return $row['code']; // 已存在则直接复用短码 } $stmt = $pdo->prepare("INSERT INTO urls (long_url, created_at) VALUES (?, NOW())"); $stmt->execute([$longUrl]); $id = $pdo->lastInsertId(); // 关键点:把自增ID转成短码,再回填到code字段 $code = dec2any($id, 62); $pdo->prepare("UPDATE urls SET code = ? WHERE id = ?")->execute([$code, $id]); return $code; }这里的设计是有讲究的。先插空记录再回填 code,而不是直接插入 code,是因为 code 依赖自增 ID 的值,而这个值只有在插入之后才知道。回填这一步如果省略,就会出现短码为空的脏数据。并发量高的场景下,lastInsertId 在同一个连接里是准确的,但如果用了连接池,就得改成先 SELECT MAX(id) 再插入,或者用事务锁。
参数说明:longUrl 入库之前要做 trim 和基本的格式校验,没有 http 前缀的加一个。否则用户输入 www.example.com 时,跳转会变成相对路径,浏览器会拼到当前域名后面,整个链接就废了。
4.2 跳转逻辑与点击计数
index.php 是短链接的入口,也是访问量最大的文件。它的逻辑不复杂:拿到 code 参数 → 查 urls 表 → 没找到就 404 → 找到了先记录日志 → 再 302 跳转。顺序不能反,很多人图省事先跳转再写日志,导致高峰期日志丢得厉害。
$code = isset($_GET['code']) ? urldecode($_GET['code']) : ''; if ($code === '') { exit('短码不能为空'); } $stmt = $pdo->prepare("SELECT id, long_url FROM urls WHERE code = ? LIMIT 1"); $stmt->execute([$code]); $url = $stmt->fetch(); if (!$url) { http_response_code(404); exit('短链接不存在或已被删除'); } // 记录点击日志:IP、UA、来源页、时间 $logStmt = $pdo->prepare( "INSERT INTO logs (url_id, ip, user_agent, referer, created_at) VALUES (?, ?, ?, ?, NOW())" ); $logStmt->execute([$url['id'], $_SERVER['REMOTE_ADDR'], substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255), $_SERVER['HTTP_REFERER'] ?? '']); // 最后才跳转,确保日志先落库 header('Location: ' . $url['long_url'], true, 302); exit;这段代码里的三个细节值得留意。第一,substr 截断 UA 是因为数据库字段长度有限,超长 UA 会导致插入失败,这种错误在浏览器端根本看不出原因,只能查日志。第二,HTTP_REFERER 要用 $_SERVER 里带的一个,别自己拼。第三,header 的第二个参数传 true,第三个参数传 302,是明确指定跳转状态码,避免 PHP 默认发 301。如果你日后要改成 301,就改这一处。
4.3 后台统计的 SQL 查询
后台管理界面里,统计页面用的 SQL 一般是按天分组统计点击量,外加一个最近点击列表。这块要拆开讲是因为很多人会把统计做成点击时实时 count(*),但短链接的日志表数据量一大,实时分组查询就变得很慢。
-- 按日期统计某条短链的点击量 SELECT DATE(created_at) AS day, COUNT(*) AS clicks FROM logs WHERE url_id = ? GROUP BY DATE(created_at) ORDER BY day DESC; -- 最近10条点击明细,带IP和来源 SELECT ip, referer, user_agent, created_at FROM logs WHERE url_id = ? ORDER BY id DESC LIMIT 10;这两条 SQL 是统计页面的主力。第一条用于画折线图,第二条用于看最近来访记录。如果点击量涨到一天几十万,GROUP BY DATE 的查询会明显变慢。常见做法是把日志表按日期做分区,或者在 urls 表上增加一个 click_count 字段,每次跳转时顺手 UPDATE click_count = click_count + 1,统计页面直接读这个字段,明细查询仍然走 logs 表,但只按需加载最近一百条。这个优化思路在这份源码上同样适用,改动量不大。
5. 常见问题与避坑记录:部署与使用的五个真实翻车现场
5.1 现象:短链接访问 404,直接访问 index.php 却正常
原因:伪静态规则没有生效。Apache 环境下常见于 AllowOverride 配置为 None,或者 .htaccess 文件权限不对;Nginx 环境下则是站点配置里没有添加 rewrite 规则。还有一种是源码包放在二级目录部署,RewriteRule 的匹配路径没加上子目录前缀,导致规则匹配不到。
解决:Apache 修改 httpd.conf 中对应目录的 AllowOverride 为 All;Nginx 的 rewrite 规则改为rewrite ^/子目录/(.*)$ /子目录/index.php?code=$1 last;。改完记得重启服务而不是重载,有些版本的 Nginx 重载不会清理旧的 rewrite 缓存。
5.2 现象:跳转时提示“您的连接不是私密连接”
原因:生成短网址的域名用的是 http,但源码后台配置的站点地址写成了 https。用户访问时被强制跳转到 https,而服务器没有配置有效的 SSL 证书,浏览器拦截了这个跳转。
解决:检查后台设置里的域名和实际访问协议是否一致。如果你确实要用 https,先去申请 SSL 证书并配置好,再在源码的配置常量中把 URL 协议改成 https。记住,半吊子的 https 比纯 http 更影响转化率,因为浏览器会直接红屏警告。
5.3 现象:并发生成短链时出现重复短码
原因:两个请求同时插入了一条相同的 long_url,因为代码里先查后插,查的时候两条都没查到,插的时候各自插了一条,拿到了不同的 ID,但回填 code 之后唯一索引报错,或者直接产生了两个指向同一条原链接但短码不同的记录。
解决:给 long_url 字段加唯一索引,然后把插入语句写成INSERT INTO urls (long_url, created_at) VALUES (?, NOW()) ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id),之后用$pdo->lastInsertId()获取原记录 ID。这样即使并发撞了,也能拿到同一个短码。这个写法在 MySQL 5.7 和 8.0 上都验证过。
5.4 现象:带中文参数的原链接跳转后变成乱码
原因:生成短链时没有对原链接做编码处理,直接存进去了。浏览器访问短链接时,跳转目标里的中文会被浏览器自动 URL 编码,但 PHP 端拿到的 long_url 是原始的中文字符串,header 里带中文会被某些浏览器拒绝或乱码。
解决:入库之前统一用rawurlencode()对整条链接编码,跳转时不处理,因为存进去的就是编码后的内容。另外数据库连接串里要加charset=utf8mb4,否则字符集不对照样乱码。还有一个隐藏点:不要用 urlencode 而要用 rawurlencode,因为 urlencode 会把空格编码成 + 号,某些服务器解析 + 号会出问题。
5.5 现象:后台登录后白屏,或者提示 undefined function
原因:大概率是 PHP 版本不兼容。代码里用了某个在较新版本中被移除的函数,比如 create_function 或 each,PHP 8.0 之后直接就 fatal error 了。
解决:先开错误日志看具体是哪个函数报错。如果是 create_function,可以用匿名函数替换;如果是 mysql_ 系列函数,需要全局替换成 PDO 或 mysqli。这类问题没有标准补丁,但常见的做法是先查error_log文件,把报错的函数名记下来,去 PHP 官方文档看它被什么替代了。项目部署前先在本地用 PHP 7.4 和 PHP 8.1 各跑一遍,是最省事的方式。
6. 进阶:把短网址改造成自己的服务——自定义短码、API 与安全加固
6.1 自定义短码:哈希冲突与长度权衡
很多场景下我们不想用系统生成的那串随机短码,而是想要一个和自己品牌相关的词,比如 /sale2025。源码里创建短码的逻辑基于自增 ID,但它也给你留了自定义入口:手动输入 code 字段。注意,自定义短码不能和现有短码撞上,所以插入前要查重。
function isCodeAvailable($pdo, $code) { $stmt = $pdo->prepare("SELECT COUNT(*) FROM urls WHERE code = ?"); $stmt->execute([$code]); return $stmt->fetchColumn() == 0; }自定义短码建议控制在 4 到 8 个字符之间。太短容易撞,太长就失去了短网址的意义。如果做营销活动,把活动关键词作为短码是一种常见做法,但要注意选择不易混淆的字符,比如去掉 0、O、l、I 这四个容易看错的字符,生成的短码在印刷物料上更好辨认。
6.2 对外提供 API:给业务系统开一个生成短链的接口
如果短网址服务只给后台手动使用,价值就少了一半。更实用的做法是开放一个 API 接口,让别的业务系统调用。源码里的 api/create.php 已经做了雏形,只需要补充鉴权逻辑,防止任何人都可以往你的库里写入链接。
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? ''; if ($apiKey !== '你的密钥') { header('Content-Type: application/json'); http_response_code(401); exit(json_encode(['error' => '无效的API密钥'])); } $longUrl = $_POST['long_url'] ?? ''; if (!preg_match('#^https?://.+$#i', $longUrl)) { exit(json_encode(['error' => '链接格式不正确'])); } $code = createShortUrl($pdo, $longUrl); header('Content-Type: application/json'); echo json_encode(['short_url' => $domain . '/' . $code]);这段代码解决了两件事:一是防止短链服务被刷,二是做了最基础的格式校验。实际接入时,建议在密钥传递上走 HTTP Header 而不是 POST 参数,因为 Header 不会出现在访问日志的请求体会里,泄密风险小一些。同时,接口调用频率限制可以用 Redis 做计数器,没条件用 Redis 的就用数据库表记录,按 IP 和 API Key 各统计一次。
6.3 安全加固:防刷、防滥用与定期清理
短网址服务最怕两件事:被刷,被滥用。被刷是指有人大量生成短链把你的数据库塞爆;被滥用是指别人用你的短链做跳转到赌博、诈骗网站的跳板。如果你的域名本身有一定权重,这种滥用会给域名信誉带来不可逆的伤害。
我自己的做法是在跳转前做一次目标域名黑白名单校验,命中黑名单的直接 403。同时给 urls 表加一个 enabled 字段,遇到被封的链接直接标记禁用,而不是删数据,这样可以保留点击日志作为证据。定期任务方面,每天凌晨清理 90 天前的 logs 表数据,保留 urls 表但把超过 180 天没有点击的记录置为禁用。这套策略可以基于 MySQL 事件调度器实现,不需要额外跑 cron。
还有一个小技巧值得说:把后台登录路径从 /admin 改成一个带随机后缀的目录,比如 /admin_x9k2。这个操作不能防住真正的高手,但可以挡住绝大多数扫描器。从那以后我每次部署这类源码,都强制走一遍同款流程:改后台路径 → 换数据库前缀 → 加 API 鉴权 → 跑一次并发测试。这套顺序做完,项目上线后的运维成本会低很多,希望帮到你。
本文还有配套的精品资源,点击获取